diff options
Diffstat (limited to 'qpid/gentools')
71 files changed, 22989 insertions, 0 deletions
diff --git a/qpid/gentools/LICENSE b/qpid/gentools/LICENSE new file mode 100644 index 0000000000..43fa6abd19 --- /dev/null +++ b/qpid/gentools/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + diff --git a/qpid/gentools/NOTICE b/qpid/gentools/NOTICE new file mode 100644 index 0000000000..09e9ae4902 --- /dev/null +++ b/qpid/gentools/NOTICE @@ -0,0 +1,2 @@ +This product includes software developed by The Apache Software Foundation (http://www.apache.org/). + diff --git a/qpid/gentools/README.txt b/qpid/gentools/README.txt new file mode 100644 index 0000000000..94f705b064 --- /dev/null +++ b/qpid/gentools/README.txt @@ -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. +================================================================================ + +AMQP MULTI_VERSION CODE GENERATOR + +This directory contains the first part of the new multi-AMQP-version code +generator. The Java generation is almost complete, C++ will follow. + +NOTE: The generator has NOT been integrated into the current build, and is +included here to run stand-alone for the purposes of review and comment. As +currently configured, this generator will not interact with any file or +directory outside of this directory. + +To build (from this directory): +rm org/apache/qpid/gentools/*.class +javac org/apache/qpid/gentools/Main.java + +Make sure you are using Sun's JDK1.5.0; Eclipse and gcj do not work. + +To run (from this directory): +java org/apache/qpid/gentools/Main -j [xml_spec_file, ...] + +XML test files are located in the xml-src directory. Pay attention to the +Basic class and Basic.Consume method - these were the primary test vehicles +for this generator. *** NOTE *** These files do not represent any current or +future version of the AMQP specification - do not use in production! + +Folders: +-------- +org/apache/qpid/gentools/: Source. +xml-src/: Test AMQP specification files. +templ.java/: Templates for java code generation. +out.java/: Output folder for generated Java files (will be created with use + of -j flag on command-line). +templ.cpp/: (Future:) Templates for C++ code generation. +out.cpp/: Output folder for generated C++ files (will be created with use + of -c flag on command-line). + +For a more detaild description of the generator, see the Qpid Wiki +(http://cwiki.apache.org/qpid/multiple-amqp-version-support.html). + +Please send comments and bugs to me (kim.vdriet [at] redhat.com) or via the +Apache Qpid list (dev [at] qpid.apache.org). + +Kim van der Riet diff --git a/qpid/gentools/build b/qpid/gentools/build new file mode 100755 index 0000000000..a18a984dff --- /dev/null +++ b/qpid/gentools/build @@ -0,0 +1,37 @@ +#!/bin/bash +# +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# + + +cd src +echo "--------- Building gentools ----------" +echo "Clearing out old build files..." +for f in org/apache/qpid/gentools/*.class; do + if [ -e $f ]; then + rm $f + fi +done +echo "Compiling..." +javac -source=1.5 -target=1.5 org/apache/qpid/gentools/*.java +echo "Done. Try it out..." +java org.apache.qpid.gentools.Main +echo "--------- Building gentools completed ----------" +cd .. diff --git a/qpid/gentools/build.xml b/qpid/gentools/build.xml new file mode 100644 index 0000000000..5d0976f56d --- /dev/null +++ b/qpid/gentools/build.xml @@ -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. + - + --> +<project name="gentools" default="compile"> + <property name="src" location="src" /> + + <property name="java.source" value="1.5"/> + <property name="java.target" value="1.5"/> + + <target name="compile"> + <javac srcdir="${src}" source="${java.source}" target="${java.target}" fork="true" debug="on"> + <classpath> + <fileset dir="${src}/../lib"> + <include name="**/*.jar"/> + </fileset> + </classpath> + </javac> + </target> + + <target name="clean"> + <delete> + <fileset dir="${src}/org/apache/qpid/gentools" includes="*.class" /> + </delete> + </target> + +</project> diff --git a/qpid/gentools/lib/LICENSE b/qpid/gentools/lib/LICENSE new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/qpid/gentools/lib/LICENSE diff --git a/qpid/gentools/lib/NOTICE b/qpid/gentools/lib/NOTICE new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/qpid/gentools/lib/NOTICE diff --git a/qpid/gentools/lib/README.txt b/qpid/gentools/lib/README.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/qpid/gentools/lib/README.txt diff --git a/qpid/gentools/lib/velocity-1.4.jar b/qpid/gentools/lib/velocity-1.4.jar Binary files differnew file mode 100644 index 0000000000..04ec9d2f85 --- /dev/null +++ b/qpid/gentools/lib/velocity-1.4.jar diff --git a/qpid/gentools/lib/velocity-dep-1.4.jar b/qpid/gentools/lib/velocity-dep-1.4.jar Binary files differnew file mode 100644 index 0000000000..375712b0e8 --- /dev/null +++ b/qpid/gentools/lib/velocity-dep-1.4.jar diff --git a/qpid/gentools/src/org/apache/qpid/gentools/AmqpClass.java b/qpid/gentools/src/org/apache/qpid/gentools/AmqpClass.java new file mode 100644 index 0000000000..26195da2e3 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/AmqpClass.java @@ -0,0 +1,197 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.gentools; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.io.PrintStream; +import java.util.Collection; + +public class AmqpClass implements Printable, NodeAware +{ + + private final AmqpVersionSet _versionSet = new AmqpVersionSet(); + private final AmqpFieldMap _fieldMap = new AmqpFieldMap(); + private final AmqpMethodMap _methodMap = new AmqpMethodMap(); + private final AmqpOrdinalVersionMap _indexMap = new AmqpOrdinalVersionMap(); + + + private final String _name; + private final Generator _generator; + + public AmqpClass(String name, Generator generator) + { + _name = name; + _generator = generator; + } + + public boolean addFromNode(Node classNode, int ordinal, AmqpVersion version) + throws AmqpParseException, AmqpTypeMappingException + { + getVersionSet().add(version); + int index = Utils.getNamedIntegerAttribute(classNode, "index"); + AmqpVersionSet indexVersionSet = getIndexMap().get(index); + if (indexVersionSet != null) + { + indexVersionSet.add(version); + } + else + { + indexVersionSet = new AmqpVersionSet(); + indexVersionSet.add(version); + getIndexMap().put(index, indexVersionSet); + } + NodeList nList = classNode.getChildNodes(); + int fieldCntr = getFieldMap().size(); + for (int i = 0; i < nList.getLength(); i++) + { + Node child = nList.item(i); + if (child.getNodeName().compareTo(Utils.ELEMENT_FIELD) == 0) + { + String fieldName = getGenerator().prepareDomainName(Utils.getNamedAttribute(child, + Utils.ATTRIBUTE_NAME)); + AmqpField thisField = getFieldMap().get(fieldName); + if (thisField == null) + { + thisField = new AmqpField(fieldName, getGenerator()); + getFieldMap().add(fieldName, thisField); + } + if (!thisField.addFromNode(child, fieldCntr++, version)) + { + String className = getGenerator().prepareClassName(Utils.getNamedAttribute(classNode, + Utils.ATTRIBUTE_NAME)); + System.out.println("INFO: Generation supression tag found for field " + + className + "." + fieldName + " - removing."); + thisField.removeVersion(version); + getFieldMap().remove(fieldName); + } + } + else if (child.getNodeName().compareTo(Utils.ELEMENT_METHOD) == 0) + { + String methodName = getGenerator().prepareMethodName(Utils.getNamedAttribute(child, + Utils.ATTRIBUTE_NAME)); + AmqpMethod thisMethod = getMethodMap().get(methodName); + if (thisMethod == null) + { + thisMethod = new AmqpMethod(methodName, getGenerator()); + getMethodMap().put(methodName, thisMethod); + } + if (!thisMethod.addFromNode(child, 0, version)) + { + String className = getGenerator().prepareClassName(Utils.getNamedAttribute(classNode, + Utils.ATTRIBUTE_NAME)); + System.out.println("INFO: Generation supression tag found for method " + + className + "." + methodName + " - removing."); + thisMethod.removeVersion(version); + getMethodMap().remove(methodName); + } + } + else if (child.getNodeName().compareTo(Utils.ELEMENT_CODEGEN) == 0) + { + String value = Utils.getNamedAttribute(child, Utils.ATTRIBUTE_VALUE); + if (value.compareTo("no-gen") == 0) + { + return false; + } + } + } + return true; + } + + public void removeVersion(AmqpVersion version) + { + getIndexMap().removeVersion(version); + getFieldMap().removeVersion(version); + getMethodMap().removeVersion(version); + getVersionSet().remove(version); + } + + public void print(PrintStream out, int marginSize, int tabSize) + { + String margin = Utils.createSpaces(marginSize); + String tab = Utils.createSpaces(tabSize); + out.println(margin + "[C] " + getName() + ": " + getVersionSet()); + + for (Integer thisIndex : getIndexMap().keySet()) + { + AmqpVersionSet indexVersionSet = getIndexMap().get(thisIndex); + out.println(margin + tab + "[I] " + thisIndex + indexVersionSet); + } + + for (String thisFieldName : getFieldMap().keySet()) + { + AmqpField thisField = getFieldMap().get(thisFieldName); + thisField.print(out, marginSize + tabSize, tabSize); + } + + for (String thisMethodName : getMethodMap().keySet()) + { + AmqpMethod thisMethod = getMethodMap().get(thisMethodName); + thisMethod.print(out, marginSize + tabSize, tabSize); + } + } + + public AmqpVersionSet getVersionSet() + { + return _versionSet; + } + + public Generator getGenerator() + { + return _generator; + } + + + public AmqpFieldMap getFieldMap() + { + return _fieldMap; + } + + + public AmqpMethodMap getMethodMap() + { + return _methodMap; + } + + public Collection<AmqpMethod> getMethods() + { + return getMethodMap().values(); + } + + + public String getName() + { + return _name; + } + + + public AmqpOrdinalVersionMap getIndexMap() + { + return _indexMap; + } + + public SingleVersionClass asSingleVersionClass(AmqpVersion version) + { + return new SingleVersionClass(this,version, _generator); + } + +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/AmqpClassMap.java b/qpid/gentools/src/org/apache/qpid/gentools/AmqpClassMap.java new file mode 100644 index 0000000000..a27a50d07e --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/AmqpClassMap.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.gentools; + +import java.util.TreeMap; + +@SuppressWarnings("serial") +public class AmqpClassMap extends TreeMap<String, AmqpClass> +{ + +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/AmqpConstant.java b/qpid/gentools/src/org/apache/qpid/gentools/AmqpConstant.java new file mode 100644 index 0000000000..df5bc6c362 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/AmqpConstant.java @@ -0,0 +1,191 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.gentools; + +import java.io.PrintStream; +import java.util.TreeMap; + +/** + * @author kpvdr + * Class to represent the <constant> declaration within the AMQP specification. + * Currently, only integer values exist within the specification, however looking forward + * to other possible types in the future, string and double types are also supported. + * <p/> + * The <constant> declaration in the specification contains only two attributes: + * name and value. + * <p/> + * The value of the constant is mapped against the version(s) for which the name is defined. + * This allows for a change in the value rather than the name only from one version to the next. + */ +@SuppressWarnings("serial") +public class AmqpConstant extends TreeMap<String, AmqpVersionSet> + implements Printable, VersionConsistencyCheck, Comparable<AmqpConstant> +{ + /** + * Constant name as defined by the name attribute of the <constant> declaration. + */ + private final String _name; + + /** + * Set of versions for which this constant name is defined. + */ + private final AmqpVersionSet _versionSet; + + /** + * Constructor + * + * @param name Constant name as defined by the name attribute of the <constant> declaration. + * @param value Constant value as defined by the value attribute of the <constant> declaration. + * @param version AMQP version for which this constant is defined + */ + public AmqpConstant(String name, String value, AmqpVersion version) + { + _name = name; + _versionSet = new AmqpVersionSet(version); + AmqpVersionSet valueVersionSet = new AmqpVersionSet(version); + put(value, valueVersionSet); + } + + + /** + * Get the name of this constant. + * + * @return Name of this constant, being the name attribute of the <constant> declaration + * represented by this class. + */ + public String getName() + { + return _name; + } + + /** + * Get the value of this constant as a String. + * + * @param version AMQP version for which this value is required. + * @return Value of this constant, being the value attribute of the <constant> declaration + * represented by this class. + * @throws AmqpTypeMappingException when a value is requested for a version for which it is not + * defined in the AMQP specifications. + */ + public String getStringValue(AmqpVersion version) + throws AmqpTypeMappingException + { + for (String thisValue : keySet()) + { + AmqpVersionSet versionSet = get(thisValue); + if (versionSet.contains(version)) + { + return thisValue; + } + } + throw new AmqpTypeMappingException("Unable to find value for constant \"" + getName() + + "\" for version " + version.toString() + "."); + } + + /** + * Get the value of this constant as an integer. + * + * @param version AMQP version for which this value is required. + * @return Value of this constant, being the value attribute of the <constant> declaration + * represented by this class. + * @throws AmqpTypeMappingException when a value is requested for a version for which it is not + * defined in the AMQP specifications. + */ + public int getIntegerValue(AmqpVersion version) + throws AmqpTypeMappingException + { + return Integer.parseInt(getStringValue(version)); + } + + /** + * Get the value of this constant as a double. + * + * @param version AMQP version for which this value is required. + * @return Value of this constant, being the value attribute of the <constant> declaration + * represented by this class. + * @throws AmqpTypeMappingException when a value is requested for a version for which it is not + * defined in the AMQP specifications. + */ + public double getDoubleValue(AmqpVersion version) + throws AmqpTypeMappingException + { + return Double.parseDouble(getStringValue(version)); + } + + /** + * Get the version set for this constant. It contains the all the versions for which this + * constant name exists. + * + * @return Set of versions for which this constant exists. + */ + public AmqpVersionSet getVersionSet() + { + return _versionSet; + } + + /* (non-Javadoc) + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + + public int compareTo(AmqpConstant other) + { + int res = getName().compareTo(other.getName()); + if (res != 0) + { + return res; + } + return getVersionSet().compareTo(other.getVersionSet()); + } + + /* (non-Javadoc) + * @see org.apache.qpid.gentools.VersionConsistencyCheck#isVersionConsistent(org.apache.qpid.gentools.AmqpVersionSet) + */ + public boolean isVersionConsistent(AmqpVersionSet globalVersionSet) + { + if (size() != 1) + { + return false; + } + return get(firstKey()).equals(globalVersionSet); + } + + /* (non-Javadoc) + * @see org.apache.qpid.gentools.Printable#print(java.io.PrintStream, int, int) + */ + public void print(PrintStream out, int marginSize, int tabSize) + { + String margin = Utils.createSpaces(marginSize); + String tab = Utils.createSpaces(tabSize); + if (size() == 1) + { + out.println(margin + tab + "[C] " + getName() + " = \"" + firstKey() + "\" " + getVersionSet()); + } + else + { + out.println(margin + tab + "[C] " + getName() + ": " + getVersionSet()); + for (String thisValue : keySet()) + { + out.println(margin + tab + tab + "= \"" + thisValue + "\" " + get(thisValue)); + } + } + } + +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/AmqpConstantSet.java b/qpid/gentools/src/org/apache/qpid/gentools/AmqpConstantSet.java new file mode 100644 index 0000000000..ab8b8be61e --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/AmqpConstantSet.java @@ -0,0 +1,152 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.gentools; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.io.PrintStream; +import java.util.Iterator; +import java.util.TreeSet; + +/** + * @author kpvdr + * This class implements a set collection for {@link AmqpConstant AmqpConstant} objects, being the collection + * of constants accumulated from various AMQP specification files processed. Each name occurs once only in the set. + * The {@link AmqpConstant AmqpConstant} objects (derived from {@link java.util.TreeMap TreeMap}) keep track of + * the value and version(s) assigned to this name. + */ +@SuppressWarnings("serial") +public class AmqpConstantSet implements Printable, NodeAware //, Comparable<AmqpConstantSet> +{ + private final LanguageConverter _converter; + private final TreeSet<AmqpConstant> _constants = new TreeSet<AmqpConstant>(); + private final AmqpVersionSet _versionSet = new AmqpVersionSet(); + + public AmqpConstantSet(LanguageConverter converter) + { + _converter = converter; + + } + + /* (non-Javadoc) + * @see org.apache.qpid.gentools.NodeAware#addFromNode(org.w3c.dom.Node, int, org.apache.qpid.gentools.AmqpVersion) + */ + public boolean addFromNode(Node node, int ordinal, AmqpVersion version) + throws AmqpParseException, AmqpTypeMappingException + { + _versionSet.add(version); + NodeList nodeList = node.getChildNodes(); + for (int i = 0; i < nodeList.getLength(); i++) + { + Node childNode = nodeList.item(i); + if (childNode.getNodeName().compareTo(Utils.ELEMENT_CONSTANT) == 0) + { + String name = getConverter().prepareConstantName(Utils.getNamedAttribute(childNode, Utils.ATTRIBUTE_NAME)); + String value = Utils.getNamedAttribute(childNode, Utils.ATTRIBUTE_VALUE); + // Find this name in the existing set of objects + boolean foundName = false; + Iterator<AmqpConstant> cItr = _constants.iterator(); + while (cItr.hasNext() && !foundName) + { + AmqpConstant thisConstant = cItr.next(); + if (name.compareTo(thisConstant.getName()) == 0) + { + foundName = true; + thisConstant.getVersionSet().add(version); + // Now, find the value in the map + boolean foundValue = false; + for (String thisValue : thisConstant.keySet()) + { + if (value.compareTo(thisValue) == 0) + { + foundValue = true; + // Add this version to existing version set. + AmqpVersionSet versionSet = thisConstant.get(thisValue); + versionSet.add(version); + } + } + // Check that the value was found - if not, add it + if (!foundValue) + { + thisConstant.put(value, new AmqpVersionSet(version)); + } + } + } + // Check that the name was found - if not, add it + if (!foundName) + { + _constants.add(new AmqpConstant(name, value, version)); + } + } + } + return true; + } + + /* (non-Javadoc) + * @see org.apache.qpid.gentools.Printable#print(java.io.PrintStream, int, int) + */ + public void print(PrintStream out, int marginSize, int tabSize) + { + out.println(Utils.createSpaces(marginSize) + "Constants: "); + for (AmqpConstant thisAmqpConstant : _constants) + { + thisAmqpConstant.print(out, marginSize, tabSize); + } + } + + /* (non-Javadoc) + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ +// public int compareTo(AmqpConstantSet other) +// { +// int res = size() - other.size(); +// if (res != 0) +// return res; +// Iterator<AmqpConstant> cItr = iterator(); +// Iterator<AmqpConstant> oItr = other.iterator(); +// while (cItr.hasNext() && oItr.hasNext()) +// { +// AmqpConstant constant = cItr.next(); +// AmqpConstant oConstant = oItr.next(); +// res = constant.compareTo(oConstant); +// if (res != 0) +// return res; +// } +// return 0; +// } + + public Iterable<? extends AmqpConstant> getContstants() + { + return _constants; + } + + public AmqpVersionSet getVersionSet() + { + return _versionSet; + } + + public LanguageConverter getConverter() + { + return _converter; + } + +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/AmqpDomain.java b/qpid/gentools/src/org/apache/qpid/gentools/AmqpDomain.java new file mode 100644 index 0000000000..ba8552a6a6 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/AmqpDomain.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.gentools; + +import java.io.PrintStream; +import java.util.TreeMap; + +@SuppressWarnings("serial") +public class AmqpDomain extends TreeMap<String, AmqpVersionSet> implements Printable +{ + private final String _domainName; + + public AmqpDomain(String domainName) + { + _domainName = domainName; + } + + public void addDomain(String domainType, AmqpVersion version) throws AmqpParseException + { + AmqpVersionSet versionSet = get(domainType); + if (versionSet == null) // First time, create new entry + { + versionSet = new AmqpVersionSet(); + put(domainType, versionSet); + } + versionSet.add(version); + } + + public String getDomainType(AmqpVersion version) + throws AmqpTypeMappingException + { + for (String thisDomainType : keySet()) + { + AmqpVersionSet versionSet = get(thisDomainType); + if (versionSet.contains(version)) + { + return thisDomainType; + } + } + throw new AmqpTypeMappingException("Unable to find version " + version + "."); + } + + public boolean hasVersion(String type, AmqpVersion v) + { + AmqpVersionSet vs = get(type); + if (vs == null) + { + return false; + } + return vs.contains(v); + } + + public void print(PrintStream out, int marginSize, int tabSize) + { + String margin = Utils.createSpaces(marginSize); + String tab = Utils.createSpaces(tabSize); + out.println(margin + getDomainName() + ":"); + + for (String thisDomainType : keySet()) + { + AmqpVersionSet vs = get(thisDomainType); + out.println(margin + tab + thisDomainType + " : " + vs.toString()); + } + } + + public String getDomainName() + { + return _domainName; + } + +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/AmqpDomainMap.java b/qpid/gentools/src/org/apache/qpid/gentools/AmqpDomainMap.java new file mode 100644 index 0000000000..0cd9d214bd --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/AmqpDomainMap.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.gentools; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.io.PrintStream; +import java.util.TreeMap; + +@SuppressWarnings("serial") +public class AmqpDomainMap extends TreeMap<String, AmqpDomain> implements Printable, NodeAware +{ + private final LanguageConverter _converter; + + public AmqpDomainMap(LanguageConverter converter) + { + _converter = converter; + + } + + public boolean addFromNode(Node n, int o, AmqpVersion v) + throws AmqpParseException, AmqpTypeMappingException + { + NodeList nl = n.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) + { + Node c = nl.item(i); + // All versions 0.9 and greater use <domain> for all domains + if (c.getNodeName().compareTo(Utils.ELEMENT_DOMAIN) == 0) + { + String domainName = getConverter().prepareDomainName(Utils.getNamedAttribute(c, Utils.ATTRIBUTE_NAME)); + String type = Utils.getNamedAttribute(c, Utils.ATTRIBUTE_TYPE); + AmqpDomain thisDomain = get(domainName); + if (thisDomain == null) + { + thisDomain = new AmqpDomain(domainName); + put(domainName, thisDomain); + } + thisDomain.addDomain(type, v); + } + // Version(s) 0.8 and earlier use <domain> for all complex domains and use + // attribute <field type=""...> for simple types. Add these simple types to + // domain list - but beware of duplicates! + else if (c.getNodeName().compareTo(Utils.ELEMENT_FIELD) == 0) + { + try + { + String type = getConverter().prepareDomainName(Utils.getNamedAttribute(c, Utils.ATTRIBUTE_TYPE)); + AmqpDomain thisDomain = get(type); + if (thisDomain == null) + { + thisDomain = new AmqpDomain(type); + put(type, thisDomain); + } + if (!thisDomain.hasVersion(type, v)) + { + thisDomain.addDomain(type, v); + } + } + catch (AmqpParseException e) + { + } // Ignore fields without type attribute + } + else if (c.getNodeName().compareTo(Utils.ELEMENT_CLASS) == 0 || + c.getNodeName().compareTo(Utils.ELEMENT_METHOD) == 0) + { + addFromNode(c, 0, v); + } + } + return true; + } + + public String getDomainType(String domainName, AmqpVersion version) + { + AmqpDomain domainType = get(domainName); + // For AMQP 8.0, primitive types were not described as domains, so + // return itself as the type. + if (domainType == null) + { + return domainName; + } + try + { + return domainType.getDomainType(version); + } + catch (AmqpTypeMappingException e) + { + throw new AmqpTypeMappingException("Unable to find domain type for domain \"" + domainName + + "\" version " + version + "."); + } + } + + + public void print(PrintStream out, int marginSize, int tabSize) + { + out.println(Utils.createSpaces(marginSize) + "Domain Map:"); + for (String thisDomainName : keySet()) + { + AmqpDomain domain = get(thisDomainName); + domain.print(out, marginSize + tabSize, tabSize); + } + } + + public LanguageConverter getConverter() + { + return _converter; + } + +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/AmqpDomainVersionMap.java b/qpid/gentools/src/org/apache/qpid/gentools/AmqpDomainVersionMap.java new file mode 100644 index 0000000000..e39550b96f --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/AmqpDomainVersionMap.java @@ -0,0 +1,62 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.gentools; + +import java.util.ArrayList; +import java.util.TreeMap; + +@SuppressWarnings("serial") +public class AmqpDomainVersionMap extends TreeMap<String, AmqpVersionSet> implements VersionConsistencyCheck +{ + public boolean isVersionConsistent(AmqpVersionSet globalVersionSet) + { + if (size() != 1) + { + return false; + } + return get(firstKey()).equals(globalVersionSet); + } + + public boolean removeVersion(AmqpVersion version) + { + Boolean res = false; + ArrayList<String> removeList = new ArrayList<String>(); + for (String domainName : keySet()) + { + AmqpVersionSet versionSet = get(domainName); + if (versionSet.contains(version)) + { + versionSet.remove(version); + if (versionSet.isEmpty()) + { + removeList.add(domainName); + } + res = true; + } + } + // Get rid of domains no longer in use + for (String domainName : removeList) + { + remove(domainName); + } + return res; + } +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/AmqpField.java b/qpid/gentools/src/org/apache/qpid/gentools/AmqpField.java new file mode 100644 index 0000000000..7c721cf913 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/AmqpField.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.gentools; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class AmqpField implements Printable, NodeAware, VersionConsistencyCheck +{ + + private final AmqpVersionSet _versionSet = new AmqpVersionSet(); + private final AmqpDomainVersionMap _domainMap = new AmqpDomainVersionMap(); + private final AmqpOrdinalVersionMap _ordinalMap = new AmqpOrdinalVersionMap(); + + private final String _name; + private final Generator _generator; + + private final Map<AmqpVersion, String> _versionToDomainMap = new HashMap<AmqpVersion, String>(); + private final Map<AmqpVersion, Integer> _versionToOrdinalMap = new HashMap<AmqpVersion, Integer>(); + + + public AmqpField(String name, Generator generator) + { + _name = name; + _generator = generator; + + } + + public boolean addFromNode(Node fieldNode, int ordinal, AmqpVersion version) + throws AmqpParseException, AmqpTypeMappingException + { + _versionSet.add(version); + String domainType; + // Early versions of the spec (8.0) used the "type" attribute instead of "domain" for some fields. + try + { + domainType = _generator.prepareDomainName(Utils.getNamedAttribute(fieldNode, Utils.ATTRIBUTE_DOMAIN)); + } + catch (AmqpParseException e) + { + domainType = _generator.prepareDomainName(Utils.getNamedAttribute(fieldNode, Utils.ATTRIBUTE_TYPE)); + } + AmqpVersionSet thisVersionList = _domainMap.get(domainType); + if (thisVersionList == null) // First time, create new entry + { + thisVersionList = new AmqpVersionSet(); + _domainMap.put(domainType, thisVersionList); + } + + _versionToDomainMap.put(version, domainType); + _versionToOrdinalMap.put(version, ordinal); + + thisVersionList.add(version); + thisVersionList = _ordinalMap.get(ordinal); + if (thisVersionList == null) // First time, create new entry + { + thisVersionList = new AmqpVersionSet(); + _ordinalMap.put(ordinal, thisVersionList); + } + thisVersionList.add(version); + NodeList nList = fieldNode.getChildNodes(); + for (int i = 0; i < nList.getLength(); i++) + { + Node child = nList.item(i); + if (child.getNodeName().compareTo(Utils.ELEMENT_CODEGEN) == 0) + { + String value = Utils.getNamedAttribute(child, Utils.ATTRIBUTE_VALUE); + if (value.compareTo("no-gen") == 0) + { + return false; + } + } + } + return true; + } + + public void removeVersion(AmqpVersion version) + { + _domainMap.removeVersion(version); + _ordinalMap.removeVersion(version); + _versionSet.remove(version); + } + + public boolean isCodeTypeConsistent(LanguageConverter converter) + throws AmqpTypeMappingException + { + if (_domainMap.size() == 1) + { + return true; // By definition + } + ArrayList<String> codeTypeList = new ArrayList<String>(); + for (String thisDomainName : _domainMap.keySet()) + { + AmqpVersionSet versionSet = _domainMap.get(thisDomainName); + String codeType = converter.getGeneratedType(thisDomainName, versionSet.first()); + if (!codeTypeList.contains(codeType)) + { + codeTypeList.add(codeType); + } + } + return codeTypeList.size() == 1; + } + + public boolean isConsistent(Generator generator) + throws AmqpTypeMappingException + { + if (!isCodeTypeConsistent(generator)) + { + return false; + } + if (_ordinalMap.size() != 1) + { + return false; + } + // Since the various doamin names map to the same code type, add the version occurrences + // across all domains to see we have all possible versions covered + int vCntr = 0; + for (String thisDomainName : _domainMap.keySet()) + { + vCntr += _domainMap.get(thisDomainName).size(); + } + return vCntr == generator.getVersionSet().size(); + } + + public boolean isTypeAndNameConsistent(Generator generator) + throws AmqpTypeMappingException + { + if (!isCodeTypeConsistent(generator)) + { + return false; + } + // Since the various doamin names map to the same code type, add the version occurrences + // across all domains to see we have all possible versions covered + int vCntr = 0; + for (String thisDomainName : _domainMap.keySet()) + { + vCntr += _domainMap.get(thisDomainName).size(); + } + return vCntr == getVersionSet().size(); + } + + + public void print(PrintStream out, int marginSize, int tabSize) + { + String margin = Utils.createSpaces(marginSize); + out.println(margin + "[F] " + _name + ": " + _versionSet); + + for (Integer thisOrdinal : _ordinalMap.keySet()) + { + AmqpVersionSet versionList = _ordinalMap.get(thisOrdinal); + out.println(margin + " [O] " + thisOrdinal + " : " + versionList.toString()); + } + + for (String thisDomainName : _domainMap.keySet()) + { + AmqpVersionSet versionList = _domainMap.get(thisDomainName); + out.println(margin + " [D] " + thisDomainName + " : " + versionList.toString()); + } + } + + public boolean isVersionConsistent(AmqpVersionSet globalVersionSet) + { + if (!_versionSet.equals(globalVersionSet)) + { + return false; + } + if (!_domainMap.isVersionConsistent(globalVersionSet)) + { + return false; + } + if (!_ordinalMap.isVersionConsistent(globalVersionSet)) + { + return false; + } + return true; + } + + + public boolean isVersionInterfaceConsistent(AmqpVersionSet globalVersionSet) + { + if (!_versionSet.equals(globalVersionSet)) + { + return false; + } + if (!_domainMap.isVersionConsistent(globalVersionSet)) + { + return false; + } + if (!_ordinalMap.isVersionConsistent(globalVersionSet)) + { + return false; + } + return true; + } + + public String getDomain(AmqpVersion version) + { + return _versionToDomainMap.get(version); + } + + public String getConsistentNativeType() + { + return _generator.getNativeType(_generator.getDomainType(getDomain(_versionSet.first()),_versionSet.first())); + } + + public int getOrdinal(AmqpVersion version) + { + return _versionToOrdinalMap.get(version); + } + + public AmqpVersionSet getVersionSet() + { + return _versionSet; + } + + public AmqpDomainVersionMap getDomainMap() + { + return _domainMap; + } + + public AmqpOrdinalVersionMap getOrdinalMap() + { + return _ordinalMap; + } + + public String getName() + { + return _name; + } + + public LanguageConverter getGenerator() + { + return _generator; + } + + public Map<AmqpVersion, String> getVersionToDomainMap() + { + return _versionToDomainMap; + } + + public Map<AmqpVersion, Integer> getVersionToOrdinalMap() + { + return _versionToOrdinalMap; + } + +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/AmqpFieldMap.java b/qpid/gentools/src/org/apache/qpid/gentools/AmqpFieldMap.java new file mode 100644 index 0000000000..0bb5e03a61 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/AmqpFieldMap.java @@ -0,0 +1,452 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.gentools; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; + +@SuppressWarnings("serial") +public class AmqpFieldMap implements VersionConsistencyCheck +{ + + private final TreeMap<String, AmqpField> _map = new TreeMap<String, AmqpField>(); + + private final AmqpVersionSet _versionSet = new AmqpVersionSet(); + + public void removeVersion(AmqpVersion version) + { + String[] fieldNameArray = new String[size()]; + _map.keySet().toArray(fieldNameArray); + Iterator<Entry<String, AmqpField>> iter = _map.entrySet().iterator(); + + while (iter.hasNext()) + { + Entry<String, AmqpField> entry = iter.next(); + entry.getValue().removeVersion(version); + iter.remove(); + } + } + + public int size() + { + return _map.size(); + + } + + public AmqpFieldMap getFieldMapForOrdinal(int ordinal) + { + AmqpFieldMap newMap = new AmqpFieldMap(); + for (AmqpField field : _map.values()) + { + + TreeMap<Integer, AmqpVersionSet> ordinalMap = field.getOrdinalMap(); + AmqpVersionSet ordinalVersions = ordinalMap.get(ordinal); + if (ordinalVersions != null) + { + newMap.add(field.getName(), field); + } + } + return newMap; + } + + public void add(String name, AmqpField field) + { + _versionSet.addAll(field.getVersionSet()); + _map.put(name, field); + } + + public AmqpOrdinalFieldMap getMapForVersion(AmqpVersion version, boolean codeTypeFlag, + LanguageConverter converter) + { + // TODO: REVIEW THIS! There may be a bug here that affects C++ generation (only with >1 version)... + // If version == null (a common scenario) then the version map is built up on the + // basis of first found item, and ignores other version variations. + // This should probably be disallowed by throwing an NPE, as AmqpOrdinalFieldMap cannot + // represent these possibilities. + // *OR* + // Change the structure of AmqpOrdianlFieldMap to allow for the various combinations that + // will result from version variation - but that is what AmqpFieldMap is... :-$ + AmqpOrdinalFieldMap ordinalFieldMap = new AmqpOrdinalFieldMap(); + for (AmqpField field : _map.values()) + { + + if (version == null || field.getVersionSet().contains(version)) + { + // 1. Search for domain name in field domain map with version that matches + String domain = ""; + boolean dFound = false; + for (String thisDomainName : field.getDomainMap().keySet()) + { + domain = thisDomainName; + AmqpVersionSet versionSet = field.getDomainMap().get(domain); + if (version == null || versionSet.contains(version)) + { + if (codeTypeFlag) + { + domain = converter.getGeneratedType(domain, version); + } + dFound = true; + } + } + + // 2. Search for ordinal in field ordianl map with version that matches + int ordinal = -1; + boolean oFound = false; + for (Integer thisOrdinal : field.getOrdinalMap().keySet()) + { + ordinal = thisOrdinal; + AmqpVersionSet versionSet = field.getOrdinalMap().get(ordinal); + if (version == null || versionSet.contains(version)) + { + oFound = true; + } + } + + if (dFound && oFound) + { + String[] fieldDomainPair = {field.getName(), domain}; + ordinalFieldMap.put(ordinal, fieldDomainPair); + } + } + } + return ordinalFieldMap; + } + + public boolean isDomainConsistent(Generator generator, AmqpVersionSet versionSet) + throws AmqpTypeMappingException + { + if (size() != 1) // Only one field for this ordinal + { + return false; + } + return _map.get(_map.firstKey()).isConsistent(generator); + } + + public int getNumFields(AmqpVersion version) + { + int fCntr = 0; + for (AmqpField field : _map.values()) + { + + if (field.getVersionSet().contains(version)) + { + fCntr++; + } + } + return fCntr; + } + + public String parseFieldMap(CommandGenerateMethod commonGenerateMethod, MangledGenerateMethod mangledGenerateMethod, + int indentSize, int tabSize, LanguageConverter converter) + { + String indent = Utils.createSpaces(indentSize); + String cr = Utils.LINE_SEPARATOR; + StringBuffer sb = new StringBuffer(); + + if (commonGenerateMethod == null) + { + // Generate warnings in code if required methods are null. + sb.append(indent + "/*********************************************************" + cr); + sb.append(indent + " * WARNING: Generated code could be missing." + cr); + sb.append(indent + " * In call to parseFieldMap(), generation method was null." + cr); + sb.append(indent + " * Check for NoSuchMethodException on startup." + cr); + sb.append(indent + " *********************************************************/" + cr); + } + + Iterator<Entry<String, AmqpField>> itr = _map.entrySet().iterator(); + while (itr.hasNext()) + { + Entry<String, AmqpField> entry = itr.next(); + String fieldName = entry.getKey(); + AmqpField field = entry.getValue(); + if (field.isCodeTypeConsistent(converter)) + { + // All versions identical - Common declaration + String domainName = field.getDomainMap().firstKey(); + AmqpVersionSet versionSet = field.getDomainMap().get(domainName); + String codeType = converter.getGeneratedType(domainName, versionSet.first()); + if (commonGenerateMethod != null) + { + sb.append(commonGenerateMethod.generate(codeType, field, versionSet, + indentSize, tabSize, itr.hasNext())); + } + } + else if (mangledGenerateMethod != null) // Version-mangled + { + sb.append(mangledGenerateMethod.generate(field, indentSize, tabSize, + itr.hasNext())); + } + } + return sb.toString(); + } + + public String parseFieldMapOrdinally(GenerateMethod generateMethod, BitFieldGenerateMethod bitGenerateMethod, + int indentSize, int tabSize, Generator codeGenerator) + { + String indent = Utils.createSpaces(indentSize); + String cr = Utils.LINE_SEPARATOR; + StringBuffer sb = new StringBuffer(); + + // Generate warnings in code if required methods are null. + if (generateMethod == null || bitGenerateMethod == null) + { + sb.append(indent + "/***********************************************" + cr); + sb.append(indent + " * WARNING: In call to parseFieldMapOrdinally():" + cr); + if (generateMethod == null) + { + sb.append(indent + " * => generateMethod is null." + cr); + } + if (bitGenerateMethod == null) + { + sb.append(indent + " * => bitGenerateMethod is null." + cr); + } + sb.append(indent + " * Generated code could be missing." + cr); + sb.append(indent + " * Check for NoSuchMethodException on startup." + cr); + sb.append(indent + " ***********************************************/" + cr); + } + + /* We must process elements in ordinal order because adjacent booleans (bits) + * must be combined into a single byte (in groups of up to 8). Start with shared + * declarations until an ordinal divergence is found. (For most methods where + * there is no difference between versions, this will simplify the generated + * code. */ + + ArrayList<String> bitFieldList = new ArrayList<String>(); + boolean ordinalDivergenceFlag = false; + int ordinal = 0; + while (ordinal < size() && !ordinalDivergenceFlag) + { + /* Since the getFieldMapOrdinal() function may map more than one Field to + * an ordinal, the number of ordinals may be less than the total number of + * fields in the fieldMap. Check for empty fieldmaps... */ + AmqpFieldMap ordinalFieldMap = getFieldMapForOrdinal(ordinal); + if (ordinalFieldMap.size() > 0) + { + if (ordinalFieldMap.isDomainConsistent(codeGenerator, getVersionSet())) + { + String fieldName = ordinalFieldMap.getFirstFieldName(); + String domain = ordinalFieldMap._map.get(fieldName).getDomainMap().firstKey(); + + String domainType = codeGenerator.getDomainType(domain, + codeGenerator.getVersionSet().first()); + + if (domainType.compareTo("bit") == 0) + { + bitFieldList.add(fieldName); + } + else if (bitFieldList.size() > 0) + { + // End of bit types - handle deferred bit type generation + if (bitGenerateMethod != null) + { + sb.append(bitGenerateMethod.generate(bitFieldList, ordinal, + indentSize, tabSize)); + } + bitFieldList.clear(); + } + if (!ordinalDivergenceFlag) + { + // Defer generation of bit types until all adjacent bits have been + // accounted for. + if (bitFieldList.size() == 0 && generateMethod != null) + { + sb.append(generateMethod.generate(domainType, fieldName, ordinal, + indentSize, tabSize)); + } + } + ordinal++; + } + else + { + ordinalDivergenceFlag = true; + } + } + } + + // Check if there is still more to do under a version-specific breakout + if (ordinalDivergenceFlag && ordinal < size()) + { + // 1. Cycle through all versions in order, create outer if(version) structure + AmqpVersion[] versionArray = new AmqpVersion[getVersionSet().size()]; + getVersionSet().toArray(versionArray); + for (int v = 0; v < versionArray.length; v++) + { + sb.append(indent); + if (v > 0) + { + sb.append("else "); + } + sb.append("if (major == " + versionArray[v].getMajor() + " && minor == " + + versionArray[v].getMinor() + ")" + cr); + sb.append(indent + "{" + cr); + + // 2. Cycle though each ordinal from where we left off in the loop above. + ArrayList<String> bitFieldList2 = new ArrayList<String>(bitFieldList); + for (int o = ordinal; o < size(); o++) + { + AmqpFieldMap ordinalFieldMap = getFieldMapForOrdinal(o); + if (ordinalFieldMap.size() > 0) + { + // 3. Cycle through each of the fields that have this ordinal. + Iterator<Map.Entry<String, AmqpField>> i = ordinalFieldMap._map.entrySet().iterator(); + while (i.hasNext()) + { + + Map.Entry<String, AmqpField> entry = i.next(); + AmqpField field = entry.getValue(); + String fieldName = entry.getKey(); + + // 4. Some fields may have more than one ordinal - match by both + // ordinal and version. + Iterator<Integer> j = field.getOrdinalMap().keySet().iterator(); + while (j.hasNext()) + { + int thisOrdinal = j.next(); + AmqpVersionSet v1 = field.getOrdinalMap().get(thisOrdinal); + if (thisOrdinal == o && v1.contains(versionArray[v])) + { + // 5. Now get the domain for this version + int domainCntr = 0; + Iterator<String> k = field.getDomainMap().keySet().iterator(); + while (k.hasNext()) + { + // Mangle domain-divergent field names + String mangledFieldName = fieldName; + if (field.getDomainMap().size() > 1) + { + mangledFieldName += "_" + (domainCntr++); + } + String domainName = k.next(); + AmqpVersionSet v2 = field.getDomainMap().get(domainName); + if (v2.contains(versionArray[v])) + { + // 6. (Finally!!) write the declaration + String domainType = codeGenerator.getDomainType(domainName, + versionArray[v]); + if (domainType.compareTo("bit") == 0) + { + bitFieldList2.add(mangledFieldName); + } + else if (bitFieldList2.size() > 0) + { + // End of bit types - handle deferred bit type generation + if (bitGenerateMethod != null) + { + sb.append(bitGenerateMethod.generate( + bitFieldList2, o, indentSize + tabSize, + tabSize)); + } + bitFieldList2.clear(); + } + // Defer generation of bit types until all adjacent bits have + // been accounted for. + if (bitFieldList2.size() == 0 && generateMethod != null) + { + sb.append(generateMethod.generate(domainType, + mangledFieldName, o, indentSize + tabSize, tabSize)); + } + } + } + } + } + } + } + } + // Check for remaining deferred bits + if (bitFieldList2.size() > 0 && bitGenerateMethod != null) + { + sb.append(bitGenerateMethod.generate(bitFieldList2, size(), + indentSize + tabSize, tabSize)); + } + sb.append(indent + "}" + cr); + } + } + // Check for remaining deferred bits + else if (bitFieldList.size() > 0 && bitGenerateMethod != null) + { + sb.append(bitGenerateMethod.generate(bitFieldList, size(), + indentSize, tabSize)); + } + return sb.toString(); + } + + private String getFirstFieldName() + { + return _map.firstKey(); + } + + public boolean isVersionConsistent(AmqpVersionSet globalVersionSet) + { + for (String thisFieldName : _map.keySet()) + { + AmqpField field = _map.get(thisFieldName); + if (!field.isVersionConsistent(globalVersionSet)) + { + return false; + } + } + return true; + } + + public boolean isVersionInterfaceConsistent(AmqpVersionSet globalVersionSet) + { + for (String thisFieldName : _map.keySet()) + { + AmqpField field = _map.get(thisFieldName); + if (!field.isVersionInterfaceConsistent(globalVersionSet)) + { + return false; + } + } + return true; + } + + + public AmqpVersionSet getVersionSet() + { + return _versionSet; + } + + public Collection<AmqpField> values() + { + return _map.values(); + } + + public AmqpField get(String fieldName) + { + return _map.get(fieldName); + } + + public void remove(String fieldName) + { + _map.remove(fieldName); + } + + public Set<String> keySet() + { + return _map.keySet(); + } +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/AmqpFlagMap.java b/qpid/gentools/src/org/apache/qpid/gentools/AmqpFlagMap.java new file mode 100644 index 0000000000..5993a1b715 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/AmqpFlagMap.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.gentools; + +import java.util.ArrayList; +import java.util.TreeMap; + +@SuppressWarnings("serial") +public class AmqpFlagMap extends TreeMap<Boolean, AmqpVersionSet> implements VersionConsistencyCheck +{ + public boolean isSet() + { + return containsKey(true); + } + + public String toString() + { + AmqpVersionSet versionSet = get(true); + if (versionSet != null) + { + return versionSet.toString(); + } + return ""; + } + + public boolean isVersionConsistent(AmqpVersionSet globalVersionSet) + { + if (size() != 1) + { + return false; + } + return get(firstKey()).equals(globalVersionSet); + } + + public boolean removeVersion(AmqpVersion version) + { + Boolean res = false; + ArrayList<Boolean> removeList = new ArrayList<Boolean>(); + for (Boolean flag : keySet()) + { + AmqpVersionSet versionSet = get(flag); + if (versionSet.contains(version)) + { + versionSet.remove(version); + if (versionSet.isEmpty()) + { + removeList.add(flag); + } + res = true; + } + } + // Get rid of flags no longer in use + for (Boolean flag : removeList) + { + remove(flag); + } + return res; + } +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/AmqpMethod.java b/qpid/gentools/src/org/apache/qpid/gentools/AmqpMethod.java new file mode 100644 index 0000000000..4ec39b209e --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/AmqpMethod.java @@ -0,0 +1,351 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.gentools; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.io.PrintStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Collection; +import java.util.concurrent.atomic.AtomicInteger; + +public class AmqpMethod implements Printable, NodeAware, VersionConsistencyCheck +{ + private final AmqpVersionSet _versionSet = new AmqpVersionSet(); + private final AmqpFieldMap _fieldMap = new AmqpFieldMap(); + + private final AmqpOrdinalVersionMap _indexMap = new AmqpOrdinalVersionMap(); + private final AmqpFlagMap _clientMethodFlagMap = new AmqpFlagMap(); // Method called on client (<chassis name="server"> in XML) + private final AmqpFlagMap _serverMethodFlagMap = new AmqpFlagMap(); // Method called on server (<chassis name="client"> in XML) + + private final Map<AmqpVersion, AmqpFieldMap> _versionToFieldsMap = new HashMap<AmqpVersion, AmqpFieldMap>(); + + private final Map<AmqpVersion, AtomicInteger> _versionToFieldCount = new HashMap<AmqpVersion, AtomicInteger>(); + + private final String _name; + private final Generator _generator; + + + public AmqpMethod(String name, Generator generator) + { + _name = name; + _generator = generator; + } + + public boolean addFromNode(Node methodNode, int ordinal, AmqpVersion version) + throws AmqpParseException, AmqpTypeMappingException + { + _versionSet.add(version); + boolean serverChassisFlag = false; + boolean clientChassisFlag = false; + int index = Utils.getNamedIntegerAttribute(methodNode, "index"); + AmqpVersionSet indexVersionSet = _indexMap.get(index); + if (indexVersionSet != null) + { + indexVersionSet.add(version); + } + else + { + indexVersionSet = new AmqpVersionSet(); + indexVersionSet.add(version); + _indexMap.put(index, indexVersionSet); + } + NodeList nList = methodNode.getChildNodes(); + AtomicInteger fieldCntr = _versionToFieldCount.get(version); + if(fieldCntr == null) + { + fieldCntr = new AtomicInteger(0); + _versionToFieldCount.put(version, fieldCntr); + } + for (int i = 0; i < nList.getLength(); i++) + { + Node child = nList.item(i); + if (child.getNodeName().compareTo(Utils.ELEMENT_FIELD) == 0) + { + String fieldName = _generator.prepareDomainName(Utils.getNamedAttribute(child, + Utils.ATTRIBUTE_NAME)); + AmqpField thisField = _fieldMap.get(fieldName); + AmqpFieldMap versionSpecificFieldMap = _versionToFieldsMap.get(version); + if (versionSpecificFieldMap == null) + { + versionSpecificFieldMap = new AmqpFieldMap(); + _versionToFieldsMap.put(version, versionSpecificFieldMap); + } + + + if (thisField == null) + { + thisField = new AmqpField(fieldName, _generator); + _fieldMap.add(fieldName, thisField); + } + + AmqpField versionSpecificField = new AmqpField(fieldName, _generator); + versionSpecificFieldMap.add(fieldName, versionSpecificField); + + versionSpecificField.addFromNode(child, fieldCntr.intValue(), version); + + if (!thisField.addFromNode(child, fieldCntr.getAndIncrement(), version)) + { + String className = _generator.prepareClassName(Utils.getNamedAttribute(methodNode.getParentNode(), + Utils.ATTRIBUTE_NAME)); + String methodName = _generator.prepareMethodName(Utils.getNamedAttribute(methodNode, + Utils.ATTRIBUTE_NAME)); + System.out.println("INFO: Generation supression tag found for field " + + className + "." + methodName + "." + fieldName + " - removing."); + thisField.removeVersion(version); + _fieldMap.remove(fieldName); + } + } + else if (child.getNodeName().compareTo(Utils.ELEMENT_CHASSIS) == 0) + { + String chassisName = Utils.getNamedAttribute(child, Utils.ATTRIBUTE_NAME); + if (chassisName.compareTo("server") == 0) + { + serverChassisFlag = true; + } + else if (chassisName.compareTo("client") == 0) + { + clientChassisFlag = true; + } + } + else if (child.getNodeName().compareTo(Utils.ELEMENT_CODEGEN) == 0) + { + String value = Utils.getNamedAttribute(child, Utils.ATTRIBUTE_VALUE); + if (value.compareTo("no-gen") == 0) + { + return false; + } + } + } + processChassisFlags(serverChassisFlag, clientChassisFlag, version); + return true; + } + + public void removeVersion(AmqpVersion version) + { + _clientMethodFlagMap.removeVersion(version); + _serverMethodFlagMap.removeVersion(version); + _indexMap.removeVersion(version); + _fieldMap.removeVersion(version); + _versionSet.remove(version); + } + + public void print(PrintStream out, int marginSize, int tabSize) + { + String margin = Utils.createSpaces(marginSize); + String tab = Utils.createSpaces(tabSize); + out.println(margin + "[M] " + _name + " {" + (_serverMethodFlagMap.isSet() ? "S " + + _serverMethodFlagMap + ( + _clientMethodFlagMap.isSet() ? ", " : "") : "") + + (_clientMethodFlagMap.isSet() + ? "C " + _clientMethodFlagMap : "") + "}" + ": " + + _versionSet); + + for (Integer thisIndex : _indexMap.keySet()) + { + AmqpVersionSet indexVersionSet = _indexMap.get(thisIndex); + out.println(margin + tab + "[I] " + thisIndex + indexVersionSet); + } + + for (String thisFieldName : _fieldMap.keySet()) + { + AmqpField thisField = _fieldMap.get(thisFieldName); + thisField.print(out, marginSize + tabSize, tabSize); + } + } + + protected void processChassisFlags(boolean serverFlag, boolean clientFlag, AmqpVersion version) + { + AmqpVersionSet versionSet = _serverMethodFlagMap.get(serverFlag); + if (versionSet != null) + { + versionSet.add(version); + } + else + { + versionSet = new AmqpVersionSet(); + versionSet.add(version); + _serverMethodFlagMap.put(serverFlag, versionSet); + } + + versionSet = _clientMethodFlagMap.get(clientFlag); + if (versionSet != null) + { + versionSet.add(version); + } + else + { + versionSet = new AmqpVersionSet(); + versionSet.add(version); + _clientMethodFlagMap.put(clientFlag, versionSet); + } + } + + public AmqpOverloadedParameterMap getOverloadedParameterLists(AmqpVersionSet globalVersionSet, + Generator generator) + throws AmqpTypeMappingException + { + AmqpOverloadedParameterMap parameterVersionMap = new AmqpOverloadedParameterMap(); + for (AmqpVersion thisVersion : globalVersionSet) + { + AmqpOrdinalFieldMap ordinalFieldMap = _fieldMap.getMapForVersion(thisVersion, true, generator); + AmqpVersionSet methodVersionSet = parameterVersionMap.get(ordinalFieldMap); + if (methodVersionSet == null) + { + methodVersionSet = new AmqpVersionSet(); + methodVersionSet.add(thisVersion); + parameterVersionMap.put(ordinalFieldMap, methodVersionSet); + } + else + { + methodVersionSet.add(thisVersion); + } + } + return parameterVersionMap; + } + + public boolean isVersionInterfaceConsistent() + { + return isVersionInterfaceConsistent(_generator.getVersionSet()); + } + + public boolean isVersionInterfaceConsistent(AmqpVersionSet globalVersionSet) + { + if (!_versionSet.equals(globalVersionSet)) + { + return false; + } + if (!_clientMethodFlagMap.isVersionConsistent(globalVersionSet)) + { + return false; + } + if (!_serverMethodFlagMap.isVersionConsistent(globalVersionSet)) + { + return false; + } + if (!_fieldMap.isVersionInterfaceConsistent(globalVersionSet)) + { + return false; + } + return true; + } + + public boolean isVersionConsistent() + { + return isVersionConsistent(_generator.getVersionSet()); + } + + + public boolean isVersionConsistent(AmqpVersionSet globalVersionSet) + { + return isVersionInterfaceConsistent(globalVersionSet) + && _indexMap.isVersionConsistent(globalVersionSet) + && _fieldMap.isVersionConsistent(globalVersionSet); + } + + public AmqpVersionSet getVersionSet() + { + return _versionSet; + } + + public AmqpFieldMap getFieldMap() + { + return _fieldMap; + } + + public AmqpOrdinalVersionMap getIndexMap() + { + return _indexMap; + } + + public AmqpFlagMap getClientMethodFlagMap() + { + return _clientMethodFlagMap; + } + + public AmqpFlagMap getServerMethodFlagMap() + { + return _serverMethodFlagMap; + } + + public Map<AmqpVersion, AmqpFieldMap> getVersionToFieldsMap() + { + return _versionToFieldsMap; + } + + public String getName() + { + return _name; + } + + public LanguageConverter getGenerator() + { + return _generator; + } + + public SingleVersionMethod asSingleVersionMethod(AmqpVersion version) + { + return new SingleVersionMethod(this, version, _generator); + } + + public Collection<AmqpField> getFields() + { + return _fieldMap.values(); + } + + public boolean isCommon(AmqpField field) + { + return field.getVersionSet().equals(getVersionSet()) && field.isTypeAndNameConsistent(_generator); + } + + public boolean isConsistentServerMethod() + { + AmqpVersionSet serverVersions = _serverMethodFlagMap.get(true); + return (serverVersions != null) && serverVersions.containsAll(_generator.getVersionSet()); + } + + + public boolean isConsistentClientMethod() + { + AmqpVersionSet clientVersions = _clientMethodFlagMap.get(true); + return (clientVersions != null) && clientVersions.containsAll(_generator.getVersionSet()); + } + + public boolean isServerMethod(AmqpVersion version) + { + AmqpVersionSet serverVersions = _serverMethodFlagMap.get(true); + return (serverVersions != null) && serverVersions.contains(version); + } + + + public boolean isClientMethod(AmqpVersion version) + { + AmqpVersionSet clientVersions = _clientMethodFlagMap.get(true); + return (clientVersions != null) && clientVersions.contains(version); + } + + public boolean inAllVersions() + { + return _versionSet.containsAll(_generator.getVersionSet()); + } +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/AmqpMethodMap.java b/qpid/gentools/src/org/apache/qpid/gentools/AmqpMethodMap.java new file mode 100644 index 0000000000..d98dab4a39 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/AmqpMethodMap.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.gentools; + +import java.util.TreeMap; + +@SuppressWarnings("serial") +public class AmqpMethodMap extends TreeMap<String, AmqpMethod> +{ + public void removeVersion(AmqpVersion version) + { + for (String methodName : keySet()) + { + get(methodName).removeVersion(version); + } + } + +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/AmqpModel.java b/qpid/gentools/src/org/apache/qpid/gentools/AmqpModel.java new file mode 100644 index 0000000000..45f0adb18d --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/AmqpModel.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.gentools; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.io.PrintStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Collection; + +public class AmqpModel implements Printable, NodeAware +{ + private final Generator _generator; + private final AmqpClassMap classMap = new AmqpClassMap(); + private final AmqpVersionSet _versionSet = new AmqpVersionSet(); + + private final Map<AmqpVersion, AmqpClassMap> _versionToClassMapMap = new HashMap<AmqpVersion, AmqpClassMap>(); + + public AmqpModel(Generator generator) + { + _generator = generator; + } + + public AmqpClassMap getAmqpClassMap(AmqpVersion version) + { + return _versionToClassMapMap.get(version); + } + + + public AmqpVersionSet getVersionSet() + { + return _versionSet; + } + + public boolean addFromNode(Node n, int o, AmqpVersion version) + throws AmqpParseException, AmqpTypeMappingException + { + _versionSet.add(version); + NodeList nList = n.getChildNodes(); + + AmqpClassMap versionSpecificClassMap = _versionToClassMapMap.get(version); + + if (versionSpecificClassMap == null) + { + versionSpecificClassMap = new AmqpClassMap(); + _versionToClassMapMap.put(version, versionSpecificClassMap); + } + + int eCntr = 0; + for (int i = 0; i < nList.getLength(); i++) + { + Node c = nList.item(i); + if (c.getNodeName().compareTo(Utils.ELEMENT_CLASS) == 0) + { + String className = _generator.prepareClassName(Utils.getNamedAttribute(c, Utils.ATTRIBUTE_NAME)); + AmqpClass thisClass = classMap.get(className); + if (thisClass == null) + { + thisClass = new AmqpClass(className, _generator); + classMap.put(className, thisClass); + } + + AmqpClass versionSpecificClass = new AmqpClass(className, _generator); + versionSpecificClassMap.put(className, versionSpecificClass); + + versionSpecificClass.addFromNode(c, eCntr, version); + + if (!thisClass.addFromNode(c, eCntr++, version)) + { + System.out.println("INFO: Generation supression tag found for class " + className + " - removing."); + thisClass.removeVersion(version); + classMap.remove(className); + } + } + } + return true; + } + + public void print(PrintStream out, int marginSize, int tabSize) + { + out.println(Utils.createSpaces(marginSize) + + "[C]=class; [M]=method; [F]=field; [D]=domain; [I]=index; [O]=ordinal" + Utils.LINE_SEPARATOR); + out.println(Utils.createSpaces(marginSize) + "Model:"); + + for (String thisClassName : classMap.keySet()) + { + AmqpClass thisClass = classMap.get(thisClassName); + thisClass.print(out, marginSize + tabSize, tabSize); + } + } + + public LanguageConverter getGenerator() + { + return _generator; + } + + public AmqpClassMap getClassMap() + { + return classMap; + } + + + public Collection<AmqpClass> getClasses() + { + return classMap.values(); + } + + public SingleVersionModel asSingleVersionModel() + { + return new SingleVersionModel(this, getVersionSet().first(), _generator); + } +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/AmqpOrdinalFieldMap.java b/qpid/gentools/src/org/apache/qpid/gentools/AmqpOrdinalFieldMap.java new file mode 100644 index 0000000000..0633eff1e1 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/AmqpOrdinalFieldMap.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.gentools; + +import java.util.Iterator; +import java.util.Set; +import java.util.TreeMap; + +@SuppressWarnings("serial") +public class AmqpOrdinalFieldMap extends TreeMap<Integer, String[]> implements Comparable +{ + + + public int compareTo(Object obj) + { + AmqpOrdinalFieldMap o = (AmqpOrdinalFieldMap) obj; + Set<Integer> thisKeySet = keySet(); + Set<Integer> oKeySet = o.keySet(); + if (!thisKeySet.equals(oKeySet)) // Not equal, but why? + { + // Size difference + int sizeDiff = thisKeySet.size() - oKeySet.size(); // -ve if this < other + if (sizeDiff != 0) + { + return sizeDiff; + } + // Conetent difference + Iterator<Integer> itr = thisKeySet.iterator(); + Iterator<Integer> oItr = oKeySet.iterator(); + while (itr.hasNext() && oItr.hasNext()) + { + int diff = itr.next() - oItr.next(); // -ve if this < other + if (diff != 0) + { + return diff; + } + } + // We should never get here... + System.err.println("AmqpOrdinalFieldMap.compareTo(): " + + "WARNING - unable to find cause of keySet difference."); + } + // Keys are equal, now check the String[]s + Iterator<Integer> itr = thisKeySet.iterator(); + Iterator<Integer> oItr = oKeySet.iterator(); + while (itr.hasNext() && oItr.hasNext()) + { + String[] thisPair = get(itr.next()); + String[] oPair = o.get(oItr.next()); + // Size difference + int sizeDiff = thisPair.length - oPair.length; // -ve if this < other + if (sizeDiff != 0) + { + return sizeDiff; + } + // Conetent difference + for (int i = 0; i < thisPair.length; i++) + { + int diff = thisPair[i].compareTo(oPair[i]); + if (diff != 0) + { + return diff; + } + } + } + return 0; + } + + public String toString() + { + StringBuffer sb = new StringBuffer(); + for (Integer thisOrdinal : keySet()) + { + String[] pair = get(thisOrdinal); + sb.append("[" + thisOrdinal + "] " + pair[0] + " : " + pair[1] + Utils.LINE_SEPARATOR); + } + return sb.toString(); + } +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/AmqpOrdinalVersionMap.java b/qpid/gentools/src/org/apache/qpid/gentools/AmqpOrdinalVersionMap.java new file mode 100644 index 0000000000..fede88631a --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/AmqpOrdinalVersionMap.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.gentools; + +import java.util.ArrayList; +import java.util.TreeMap; + +@SuppressWarnings("serial") +public class AmqpOrdinalVersionMap extends TreeMap<Integer, AmqpVersionSet> implements VersionConsistencyCheck +{ + public boolean isVersionConsistent(AmqpVersionSet globalVersionSet) + { + if (size() != 1) + { + return false; + } + return get(firstKey()).equals(globalVersionSet); + } + + public int getOrdinal(AmqpVersion version) + throws AmqpTypeMappingException + { + for (Integer thisOrdinal : keySet()) + { + AmqpVersionSet versionSet = get(thisOrdinal); + if (versionSet.contains(version)) + { + return thisOrdinal; + } + } + throw new AmqpTypeMappingException("Unable to locate version " + version + " in ordianl version map."); + } + + public boolean removeVersion(AmqpVersion version) + { + Boolean res = false; + ArrayList<Integer> removeList = new ArrayList<Integer>(); + for (Integer ordinal : keySet()) + { + AmqpVersionSet versionSet = get(ordinal); + if (versionSet.contains(version)) + { + versionSet.remove(version); + if (versionSet.isEmpty()) + { + removeList.add(ordinal); + } + res = true; + } + } + // Get rid of ordinals no longer in use + for (Integer ordinal : removeList) + { + remove(ordinal); + } + return res; + } +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/AmqpOverloadedParameterMap.java b/qpid/gentools/src/org/apache/qpid/gentools/AmqpOverloadedParameterMap.java new file mode 100644 index 0000000000..10978d0e4a --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/AmqpOverloadedParameterMap.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.gentools; + +import java.util.TreeMap; + +@SuppressWarnings("serial") +public class AmqpOverloadedParameterMap extends TreeMap<AmqpOrdinalFieldMap, AmqpVersionSet> +{ + +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/AmqpParseException.java b/qpid/gentools/src/org/apache/qpid/gentools/AmqpParseException.java new file mode 100644 index 0000000000..3f3d4611fc --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/AmqpParseException.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.gentools; + +@SuppressWarnings("serial") +public class AmqpParseException extends RuntimeException +{ + public AmqpParseException(String msg) + { + super(msg); + } +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/AmqpTemplateException.java b/qpid/gentools/src/org/apache/qpid/gentools/AmqpTemplateException.java new file mode 100644 index 0000000000..1ac09ea453 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/AmqpTemplateException.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.gentools; + +@SuppressWarnings("serial") +public class AmqpTemplateException extends RuntimeException +{ + public AmqpTemplateException(String msg) + { + super(msg); + } +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/AmqpTypeMappingException.java b/qpid/gentools/src/org/apache/qpid/gentools/AmqpTypeMappingException.java new file mode 100644 index 0000000000..127a8835b0 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/AmqpTypeMappingException.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.gentools; + +@SuppressWarnings("serial") +public class AmqpTypeMappingException extends RuntimeException +{ + public AmqpTypeMappingException(String msg) + { + super(msg); + } +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/AmqpVersion.java b/qpid/gentools/src/org/apache/qpid/gentools/AmqpVersion.java new file mode 100644 index 0000000000..dbeef1b895 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/AmqpVersion.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.gentools; + +public class AmqpVersion implements Comparable<AmqpVersion> +{ + private final int _major; + private final int _minor; + + public AmqpVersion(int major, int minor) + { + _major = major; + _minor = minor; + } + + public AmqpVersion(AmqpVersion version) + { + _major = version.getMajor(); + _minor = version.getMinor(); + } + + public int getMajor() + { + return _major; + } + + public int getMinor() + { + return _minor; + } + + public int compareTo(AmqpVersion v) + { + if (_major != v.getMajor()) + { + return _major - v.getMajor(); + } + if (_minor != v.getMinor()) + { + return _minor - v.getMinor(); + } + return 0; + } + + public String namespace() + { + return "ver_" + _major + "_" + _minor; + } + + public String toString() + { + return _major + "-" + _minor; + } +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/AmqpVersionSet.java b/qpid/gentools/src/org/apache/qpid/gentools/AmqpVersionSet.java new file mode 100644 index 0000000000..6419e23a1e --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/AmqpVersionSet.java @@ -0,0 +1,79 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.gentools; + +import java.io.PrintStream; +import java.util.Iterator; +import java.util.TreeSet; + +@SuppressWarnings("serial") +public class AmqpVersionSet extends TreeSet<AmqpVersion> implements Printable, Comparable<AmqpVersionSet> +{ + public AmqpVersionSet() + { + super(); + } + + public AmqpVersionSet(AmqpVersion version) + { + super(); + add(version); + } + + public AmqpVersion find(AmqpVersion version) + { + for (AmqpVersion v : this) + { + if (v.compareTo(version) == 0) + { + return v; + } + } + return null; + } + + public void print(PrintStream out, int marginSize, int tabSize) + { + out.print(Utils.createSpaces(marginSize) + "Version Set: " + toString() + Utils.LINE_SEPARATOR); + } + + public int compareTo(AmqpVersionSet other) + { + int res = size() - other.size(); + if (res != 0) + { + return res; + } + Iterator<AmqpVersion> vItr = iterator(); + Iterator<AmqpVersion> oItr = other.iterator(); + while (vItr.hasNext() && oItr.hasNext()) + { + AmqpVersion version = vItr.next(); + AmqpVersion oVersion = oItr.next(); + res = version.compareTo(oVersion); + if (res != 0) + { + return res; + } + } + return 0; + } +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/BitFieldGenerateMethod.java b/qpid/gentools/src/org/apache/qpid/gentools/BitFieldGenerateMethod.java new file mode 100644 index 0000000000..d85510ee98 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/BitFieldGenerateMethod.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.gentools; + + +import java.util.List; + +public interface BitFieldGenerateMethod +{ + String generate(List<String> bitFieldList, int ordinal, int indentSize, int tabSize); +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/CommandGenerateMethod.java b/qpid/gentools/src/org/apache/qpid/gentools/CommandGenerateMethod.java new file mode 100644 index 0000000000..641f50c3f8 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/CommandGenerateMethod.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.gentools; + +public interface CommandGenerateMethod +{ + String generate(String codeType, AmqpField field, AmqpVersionSet versionSet, int indentSize, int tabSize, boolean notLast); +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/ConsolidatedField.java b/qpid/gentools/src/org/apache/qpid/gentools/ConsolidatedField.java new file mode 100644 index 0000000000..9ab7eb178b --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/ConsolidatedField.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.gentools; + +import java.util.List; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + +/** + * Created by IntelliJ IDEA. + * User: U146758 + * Date: 06-Mar-2007 + * Time: 09:22:21 + * To change this template use File | Settings | File Templates. + */ +public class ConsolidatedField +{ + private final String _name; + private final String _type; + private final List<String> _underlyingFields = new ArrayList<String>(); + private final Generator _generator; + private boolean _isConsolidated; + + public ConsolidatedField(Generator generator, String name, String type) + { + this(generator,name,type,name,false); + } + + public ConsolidatedField(Generator generator, String name, String type, String firstField) + { + this(generator,name,type,firstField,true); + } + + public ConsolidatedField(Generator generator, String name, String type, String firstField, boolean consolidated) + { + + _generator = generator; + _name = name; + _type = type; + _isConsolidated = consolidated; + _underlyingFields.add(firstField); + + } + + + public void setConsolidated(boolean consolidated) + { + _isConsolidated = consolidated; + } + + public String getName() + { + return _name; + } + + public String getType() + { + return _type; + } + + public String getNativeType() + { + return _generator.getNativeType(_type); + } + + public String getEncodingType() + { + return _generator.getEncodingType(_type); + } + + public void add(String name) + { + _underlyingFields.add(name); + } + + public Collection<String> getUnderlyingFields() + { + return Collections.unmodifiableCollection(_underlyingFields); + } + + public int getPosition(String fieldName) + { + return _underlyingFields.indexOf(fieldName); + } + + public boolean isConsolidated() + { + return _isConsolidated; + } + + public boolean isFixedSize() + { + return _generator.isFixedSizeType( getType() ); + } + + public int getSize() + { + return _generator.getTypeSize( getType() ); + } + +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/CppGenerator.java b/qpid/gentools/src/org/apache/qpid/gentools/CppGenerator.java new file mode 100644 index 0000000000..4f58cba34e --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/CppGenerator.java @@ -0,0 +1,1716 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.gentools; + +import java.io.File; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.TreeMap; + +public class CppGenerator extends Generator +{ + protected static final String versionNamespaceStartToken = "${version_namespace_start}"; + protected static final String versionNamespaceEndToken = "${version_namespace_end}"; + + // TODO: Move this to parent class + protected static final int FIELD_NAME = 0; + protected static final int FIELD_CODE_TYPE = 1; + + /** + * A complete list of C++ reserved words. The names of varous XML elements within the AMQP + * specification file are used for C++ identifier names in the generated code. Each proposed + * name is checked against this list and is modified (by adding an '_' to the end of the + * name - see function parseForReservedWords()) if found to be present. + */ + protected static final String[] cppReservedWords = {"and", "and_eq", "asm", "auto", "bitand", + "bitor", "bool", "break", "case", "catch", "char", "class", "compl", "const", "const_cast", + "continue", "default", "delete", "do", "DomainInfo", "double", "dynamic_cast", "else", + "enum", "explicit", "extern", "false", "float", "for", "friend", "goto", "if", "inline", + "int", "long", "mutable", "namespace", "new", "not", "not_eq", "operator", "or", "or_eq", + "private", "protected", "public", "register", "reinterpret_cast", "return", "short", + "signed", "sizeof", "static", "static_cast", "struct", "switch", "template", "this", + "throw", "true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using", + "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq"}; + + /** + * Although not reserved words, the following list of variable names that may cause compile + * problems within a C++ environment because they clash with common #includes. The names of + * varous XML elements within the AMQP specification file are used for C++ identifier names + * in the generated code. Each proposed name is checked against this list and is modified + * (by adding an '_' to the end of the name - see function parseForReservedWords()) if found + * to be present. This list is best added to on an as-needed basis. + */ + protected static final String[] cppCommonDefines = {"string"}; + + // TODO: Move this to the Generator superclass? + protected boolean quietFlag; // Supress warning messages to the console + + private class DomainInfo + { + public String type; + public String size; + public String encodeExpression; + public String decodeExpression; + + public DomainInfo(String domain, String size, String encodeExpression, + String decodeExpression) + { + this.type = domain; + this.size = size; + this.encodeExpression = encodeExpression; + this.decodeExpression = decodeExpression; + } + } + + private static TreeMap<String, DomainInfo> typeMap = new TreeMap<String, DomainInfo>(); + + public CppGenerator() + { + super(); + quietFlag = true; + // Load C++ type and size maps. + // Adjust or add to these lists as new types are added/defined. + // The char '#' will be replaced by the field variable name (any type). + // The char '~' will be replaced by the compacted bit array size (type bit only). + typeMap.put("bit", new DomainInfo( + "bool", // type + "~", // size + "", // encodeExpression + "")); // decodeExpression + typeMap.put("content", new DomainInfo( + "Content", // type + "#.size()", // size + "buffer.putContent(#)", // encodeExpression + "buffer.getContent(#)")); // decodeExpression + typeMap.put("long", new DomainInfo( + "u_int32_t", // type + "4", // size + "buffer.putLong(#)", // encodeExpression + "# = buffer.getLong()")); // decodeExpression + typeMap.put("longlong", new DomainInfo( + "u_int64_t", // type + "8", // size + "buffer.putLongLong(#)", // encodeExpression + "# = buffer.getLongLong()")); // decodeExpression + typeMap.put("longstr", new DomainInfo( + "string", // type + "4 + #.length()", // size + "buffer.putLongString(#)", // encodeExpression + "buffer.getLongString(#)")); // decodeExpression + typeMap.put("octet", new DomainInfo( + "u_int8_t", // type + "1", // size + "buffer.putOctet(#)", // encodeExpression + "# = buffer.getOctet()")); // decodeExpression + typeMap.put("short", new DomainInfo( + "u_int16_t", // type + "2", // size + "buffer.putShort(#)", // encodeExpression + "# = buffer.getShort()")); // decodeExpression + typeMap.put("shortstr", new DomainInfo( + "string", // type + "1 + #.length()", // size + "buffer.putShortString(#)", // encodeExpression + "buffer.getShortString(#)")); // decodeExpression + typeMap.put("table", new DomainInfo( + "FieldTable", // type + "#.size()", // size + "buffer.putFieldTable(#)", // encodeExpression + "buffer.getFieldTable(#)")); // decodeExpression + typeMap.put("timestamp", new DomainInfo( + "u_int64_t", // type + "8", // size + "buffer.putLongLong(#)", // encodeExpression + "buffer.getLongLong(#)")); // decodeExpression + } + + + public boolean isQuietFlag() + { + return quietFlag; + } + + public void setQuietFlag(boolean quietFlag) + { + this.quietFlag = quietFlag; + } + + // === Start of methods for Interface LanguageConverter === + + public String prepareClassName(String className) + { + return camelCaseName(className, true); + } + + public String prepareMethodName(String methodName) + { + return camelCaseName(methodName, false); + } + + public String prepareDomainName(String domainName) + { + return camelCaseName(domainName, false); + } + + + public String getGeneratedType(String domainName, AmqpVersion version) + throws AmqpTypeMappingException + { + String domainType = getDomainType(domainName, version); + if (domainType == null) + { + throw new AmqpTypeMappingException("Domain type \"" + domainName + + "\" not found in C++ typemap."); + } + DomainInfo info = typeMap.get(domainType); + if (info == null) + { + throw new AmqpTypeMappingException("Unknown domain: \"" + domainType + "\""); + } + return info.type; + } + + // === Abstract methods from class Generator - C++-specific implementation === + + @Override + protected String prepareFilename(String filenameTemplate, AmqpClass thisClass, AmqpMethod method, + AmqpField field, AmqpVersion version) + { + StringBuffer sb = new StringBuffer(filenameTemplate); + if (thisClass != null) + { + replaceToken(sb, "${CLASS}", thisClass.getName()); + } + if (method != null) + { + replaceToken(sb, "${METHOD}", method.getName()); + } + if (field != null) + { + replaceToken(sb, "${FIELD}", field.getName()); + } + return sb.toString(); + } + + @Override + protected void processModelTemplate(NamedTemplate template) + { + processTemplate(template, null, null, null, null); + } + + @Override + protected void processClassTemplate(NamedTemplate template, AmqpClass thisClass) + { + processTemplate(template, thisClass, null, null, null); + } + + @Override + protected void processMethodTemplate(NamedTemplate template, AmqpClass thisClass, + AmqpMethod method) + { + StringBuffer sb = new StringBuffer(template.getTemplate()); + String filename = prepareFilename(getTemplateFileName(sb), thisClass, method, null, null); + boolean templateProcessedFlag = false; + + // If method is not version consistent, create a namespace for each version + // i.e. copy the bit between the versionNamespaceStartToken and versionNamespaceEndToken + // once for each namespace. + if (method != null) + { + if (!method.isVersionConsistent(getVersionSet())) + { + int namespaceStartIndex = sb.indexOf(versionNamespaceStartToken); + int namespaceEndIndex = sb.indexOf(versionNamespaceEndToken) + + versionNamespaceEndToken.length(); + if (namespaceStartIndex >= 0 && namespaceEndIndex >= 0 && + namespaceStartIndex <= namespaceEndIndex) + { + String namespaceSpan = sb.substring(namespaceStartIndex, namespaceEndIndex) + CR; + sb.delete(namespaceStartIndex, namespaceEndIndex); + for (AmqpVersion v : method.getVersionSet()) + { + StringBuffer nssb = new StringBuffer(namespaceSpan); + processTemplate(nssb, thisClass, method, null, template.getName(), v); + sb.insert(namespaceStartIndex, nssb); + } + // Process all tokens *not* within the namespace span prior to inserting namespaces + processTemplate(sb, thisClass, method, null, template.getName(), null); + } + templateProcessedFlag = true; + } + } + // Remove any remaining namespace tags + int nsTokenIndex = sb.indexOf(versionNamespaceStartToken); + while (nsTokenIndex > 0) + { + sb.delete(nsTokenIndex, nsTokenIndex + versionNamespaceStartToken.length()); + nsTokenIndex = sb.indexOf(versionNamespaceStartToken); + } + nsTokenIndex = sb.indexOf(versionNamespaceEndToken); + while (nsTokenIndex > 0) + { + sb.delete(nsTokenIndex, nsTokenIndex + versionNamespaceEndToken.length()); + nsTokenIndex = sb.indexOf(versionNamespaceEndToken); + } + + if (!templateProcessedFlag) + { + processTemplate(sb, thisClass, method, null, template.getName(), null); + } + writeTargetFile(sb, new File(getOutputDirectory() + Utils.FILE_SEPARATOR + filename)); + generatedFileCounter++; + } + + @Override + protected void processTemplate(NamedTemplate template, AmqpClass thisClass, AmqpMethod method, + AmqpField field, AmqpVersion version) + { + StringBuffer sb = new StringBuffer(template.getTemplate()); + String filename = prepareFilename(getTemplateFileName(sb), thisClass, method, field, version); + processTemplate(sb, thisClass, method, field, template.getName(), null); + writeTargetFile(sb, new File(getOutputDirectory() + Utils.FILE_SEPARATOR + filename)); + generatedFileCounter++; + } + + protected void processTemplate(StringBuffer sb, AmqpClass thisClass, AmqpMethod method, + AmqpField field, String templateFileName, AmqpVersion version) + { + try + { + processAllLists(sb, thisClass, method, version); + } + catch (AmqpTemplateException e) + { + System.out.println("ERROR: " + templateFileName + ": " + e.getMessage()); + } + try + { + processAllTokens(sb, thisClass, method, field, version); + } + catch (AmqpTemplateException e) + { + System.out.println("ERROR: " + templateFileName + ": " + e.getMessage()); + } + } + + @Override + protected String processToken(String token, AmqpClass thisClass, AmqpMethod method, AmqpField field, + AmqpVersion version) + { + if (token.compareTo("${GENERATOR}") == 0) + { + return GENERATOR_INFO; + } + if (token.compareTo("${CLASS}") == 0 && thisClass != null) + { + return thisClass.getName(); + } + if (token.compareTo("${CLASS_ID_INIT}") == 0 && thisClass != null) + { + if (version == null) + { + return String.valueOf(thisClass.getIndexMap().firstKey()); + } + return getIndex(thisClass.getIndexMap(), version); + } + if (token.compareTo("${METHOD}") == 0 && method != null) + { + return method.getName(); + } + if (token.compareTo("${METHOD_ID_INIT}") == 0 && method != null) + { + if (version == null) + { + return String.valueOf(method.getIndexMap().firstKey()); + } + return getIndex(method.getIndexMap(), version); + } + if (token.compareTo("${FIELD}") == 0 && field != null) + { + return field.getName(); + } + if (token.compareTo(versionNamespaceStartToken) == 0 && version != null) + { + return "namespace " + version.namespace() + CR + "{"; + } + if (token.compareTo(versionNamespaceEndToken) == 0 && version != null) + { + return "} // namespace " + version.namespace(); + } + if (token.compareTo("${mb_constructor_with_initializers}") == 0) + { + return generateConstructor(thisClass, method, version, 4, 4); + } + if (token.compareTo("${mb_server_operation_invoke}") == 0) + { + return generateServerOperationsInvoke(thisClass, method, version, 4, 4); + } + if (token.compareTo("${mb_buffer_param}") == 0) + { + return method.getFieldMap().size() > 0 ? " buffer" : ""; + } + if (token.compareTo("${hv_latest_major}") == 0) + { + return String.valueOf(getVersionSet().last().getMajor()); + } + if (token.compareTo("${hv_latest_minor}") == 0) + { + return String.valueOf(getVersionSet().last().getMinor()); + } + + throw new AmqpTemplateException("Template token " + token + " unknown."); + } + + @Override + protected void processClassList(StringBuffer sb, int listMarkerStartIndex, int listMarkerEndIndex, + AmqpModel model, AmqpVersion version) + { + String codeSnippet; + int lend = sb.indexOf(CR, listMarkerStartIndex) + 1; // Include cr at end of line + String tline = sb.substring(listMarkerEndIndex, lend); // Line excluding line marker, including cr + int tokxStart = tline.indexOf('$'); + String token = tline.substring(tokxStart).trim(); + sb.delete(listMarkerStartIndex, lend); + + // ClientOperations.h + if (token.compareTo("${coh_method_handler_get_method}") == 0) + { + codeSnippet = generateOpsMethodHandlerGetMethods(model, false, 4); + } + else if (token.compareTo("${coh_inner_class}") == 0) + { + codeSnippet = generateOpsInnerClasses(model, false, 4, 4); + } + + // ServerOperations.h + else if (token.compareTo("${soh_method_handler_get_method}") == 0) + { + codeSnippet = generateOpsMethodHandlerGetMethods(model, true, 4); + } + else if (token.compareTo("${soh_inner_class}") == 0) + { + codeSnippet = generateOpsInnerClasses(model, true, 4, 4); + } + + // ClientProxy.h/cpp + else if (token.compareTo("${cph_inner_class_instance}") == 0) + { + codeSnippet = generateProxyInnerClassInstances(model, false, 4); + } + else if (token.compareTo("${cph_inner_class_get_method}") == 0) + { + codeSnippet = generateProxyInnerClassGetMethodDecls(model, false, 4); + } + else if (token.compareTo("${cph_inner_class_defn}") == 0) + { + codeSnippet = generateProxyInnerClassDefinitions(model, false, 4, 4); + } + else if (token.compareTo("${cpc_constructor_initializer}") == 0) + { + codeSnippet = generateProxyConstructorInitializers(model, false, 4); + } + else if (token.compareTo("${cpc_inner_class_get_method}") == 0) + { + codeSnippet = generateProxyInnerClassGetMethodImpls(model, false, 0, 4); + } + else if (token.compareTo("${cpc_inner_class_impl}") == 0) + { + codeSnippet = generateProxyInnerClassImpl(model, false, 0, 4); + } + else if (token.compareTo("${cph_handler_pointer_defn}") == 0) + { + codeSnippet = generateHandlerPointerDefinitions(model, false, 4); + } + else if (token.compareTo("${cph_handler_pointer_get_method}") == 0) + { + codeSnippet = generateHandlerPointerGetMethods(model, false, 4); + } + + // SerrverProxy.h/cpp + else if (token.compareTo("${sph_inner_class_instance}") == 0) + { + codeSnippet = generateProxyInnerClassInstances(model, true, 4); + } + else if (token.compareTo("${sph_inner_class_get_method}") == 0) + { + codeSnippet = generateProxyInnerClassGetMethodDecls(model, true, 4); + } + else if (token.compareTo("${sph_inner_class_defn}") == 0) + { + codeSnippet = generateProxyInnerClassDefinitions(model, true, 4, 4); + } + else if (token.compareTo("${spc_constructor_initializer}") == 0) + { + codeSnippet = generateProxyConstructorInitializers(model, true, 4); + } + else if (token.compareTo("${spc_inner_class_get_method}") == 0) + { + codeSnippet = generateProxyInnerClassGetMethodImpls(model, true, 0, 4); + } + else if (token.compareTo("${spc_inner_class_impl}") == 0) + { + codeSnippet = generateProxyInnerClassImpl(model, true, 0, 4); + } + else if (token.compareTo("${sph_handler_pointer_defn}") == 0) + { + codeSnippet = generateHandlerPointerDefinitions(model, true, 4); + } + else if (token.compareTo("${sph_handler_pointer_get_method}") == 0) + { + codeSnippet = generateHandlerPointerGetMethods(model, true, 4); + } + + // amqp_methods.h/cpp + else if (token.compareTo("${mh_method_body_class_indlude}") == 0) + { + codeSnippet = generateMethodBodyIncludeList(model, 0); + } + else if (token.compareTo("${mh_method_body_class_instance}") == 0) + { + codeSnippet = generateMethodBodyInstances(model, 0); + } + else if (token.compareTo("${mc_create_method_body_map_entry}") == 0) + { + codeSnippet = generateMethodBodyMapEntry(model, 4); + } + + else // Oops! + { + throw new AmqpTemplateException("Template token \"" + token + "\" unknown."); + } + sb.insert(listMarkerStartIndex, codeSnippet); + } + + @Override + protected void processMethodList(StringBuffer sb, int listMarkerStartIndex, int listMarkerEndIndex, + AmqpClass thisClass) + { + String codeSnippet; + int lend = sb.indexOf(CR, listMarkerStartIndex) + 1; // Include cr at end of line + String tline = sb.substring(listMarkerEndIndex, lend); // Line excluding line marker, including cr + int tokxStart = tline.indexOf('$'); + String token = tline.substring(tokxStart).trim(); + sb.delete(listMarkerStartIndex, lend); + + if (token.compareTo("${cpc_method_body_include}") == 0) + { + codeSnippet = generateMethodBodyIncludes(thisClass, 0); + } + else if (token.compareTo("${spc_method_body_include}") == 0) + { + codeSnippet = generateMethodBodyIncludes(thisClass, 0); + } + else if (token.compareTo("${mc_method_body_include}") == 0) + { + codeSnippet = generateMethodBodyIncludes(thisClass, 0); + } + + else // Oops! + { + throw new AmqpTemplateException("Template token " + token + " unknown."); + } + sb.insert(listMarkerStartIndex, codeSnippet); + } + + @Override + protected void processFieldList(StringBuffer sb, int listMarkerStartIndex, int listMarkerEndIndex, + AmqpFieldMap fieldMap, AmqpVersion version) + { + String codeSnippet; + int lend = sb.indexOf(CR, listMarkerStartIndex) + 1; // Include cr at end of line + String tline = sb.substring(listMarkerEndIndex, lend); // Line excluding line marker, including cr + int tokxStart = tline.indexOf('$'); + String token = tline.substring(tokxStart).trim(); + sb.delete(listMarkerStartIndex, lend); + + if (token.compareTo("${mb_field_declaration}") == 0) + { + codeSnippet = generateFieldDeclarations(fieldMap, version, 4); + } + else if (token.compareTo("${mb_field_get_method}") == 0) + { + codeSnippet = generateFieldGetMethods(fieldMap, version, 4); + } + else if (token.compareTo("${mb_field_print}") == 0) + { + codeSnippet = generatePrintMethodContents(fieldMap, version, 8); + } + else if (token.compareTo("${mb_body_size}") == 0) + { + codeSnippet = generateBodySizeMethodContents(fieldMap, version, 8); + } + else if (token.compareTo("${mb_encode}") == 0) + { + codeSnippet = generateEncodeMethodContents(fieldMap, version, 8); + } + else if (token.compareTo("${mb_decode}") == 0) + { + codeSnippet = generateDecodeMethodContents(fieldMap, version, 8); + } + + else // Oops! + { + throw new AmqpTemplateException("Template token " + token + " unknown."); + } + sb.insert(listMarkerStartIndex, codeSnippet); + } + + @Override + protected void processConstantList(StringBuffer sb, int listMarkerStartIndex, int listMarkerEndIndex, + AmqpConstantSet constantSet) + { + String codeSnippet; + int lend = sb.indexOf(CR, listMarkerStartIndex) + 1; // Include cr at end of line + String tline = sb.substring(listMarkerEndIndex, lend); // Line excluding line marker, including cr + int tokxStart = tline.indexOf('$'); + String token = tline.substring(tokxStart).trim(); + sb.delete(listMarkerStartIndex, lend); + + if (token.compareTo("${ch_get_value_method}") == 0) + { + codeSnippet = generateConstantGetMethods(constantSet, 4, 4); + } + + else // Oops! + { + throw new AmqpTemplateException("Template token " + token + " unknown."); + } + sb.insert(listMarkerStartIndex, codeSnippet); + } + + // === Protected and private helper functions unique to C++ implementation === + + // Methods for generation of code snippets for AMQP_Constants.h file + + protected String generateConstantGetMethods(AmqpConstantSet constantSet, + int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + for (AmqpConstant thisConstant : constantSet.getContstants()) + { + if (thisConstant.isVersionConsistent(getVersionSet())) + { + // return a constant + String value = thisConstant.firstKey(); + sb.append(indent + "static const char* " + thisConstant.getName() + "() { return \"" + + thisConstant.firstKey() + "\"; }" + CR); + if (Utils.containsOnlyDigits(value)) + { + sb.append(indent + "static int " + thisConstant.getName() + "AsInt() { return " + + thisConstant.firstKey() + "; }" + CR); + } + if (Utils.containsOnlyDigitsAndDecimal(value)) + { + sb.append(indent + "static double " + thisConstant.getName() + "AsDouble() { return (double)" + + thisConstant.firstKey() + "; }" + CR); + } + sb.append(CR); + } + else + { + // Return version-specific constant + sb.append(generateVersionDependentGet(thisConstant, "const char*", "", "\"", "\"", indentSize, tabSize)); + sb.append(generateVersionDependentGet(thisConstant, "int", "AsInt", "", "", indentSize, tabSize)); + sb.append(generateVersionDependentGet(thisConstant, "double", "AsDouble", "(double)", "", indentSize, tabSize)); + sb.append(CR); + } + } + return sb.toString(); + } + + protected String generateVersionDependentGet(AmqpConstant constant, String methodReturnType, + String methodNameSuffix, String returnPrefix, String returnPostfix, int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + StringBuffer sb = new StringBuffer(); + sb.append(indent + methodReturnType + " " + constant.getName() + methodNameSuffix + + "() const" + CR); + sb.append(indent + "{" + CR); + boolean first = true; + for (String thisValue : constant.keySet()) + { + AmqpVersionSet versionSet = constant.get(thisValue); + sb.append(indent + tab + (first ? "" : "else ") + "if (" + generateVersionCheck(versionSet) + + ")" + CR); + sb.append(indent + tab + "{" + CR); + if (methodReturnType.compareTo("int") == 0 && !Utils.containsOnlyDigits(thisValue)) + { + sb.append(generateConstantDeclarationException(constant.getName(), methodReturnType, + indentSize + (2 * tabSize), tabSize)); + } + else if (methodReturnType.compareTo("double") == 0 && !Utils.containsOnlyDigitsAndDecimal(thisValue)) + { + sb.append(generateConstantDeclarationException(constant.getName(), methodReturnType, + indentSize + (2 * tabSize), tabSize)); + } + else + { + sb.append(indent + tab + tab + "return " + returnPrefix + thisValue + returnPostfix + ";" + CR); + } + sb.append(indent + tab + "}" + CR); + first = false; + } + sb.append(indent + tab + "else" + CR); + sb.append(indent + tab + "{" + CR); + sb.append(indent + tab + tab + "std::stringstream ss;" + CR); + sb.append(indent + tab + tab + "ss << \"Constant \\\"" + constant.getName() + + "\\\" is undefined for AMQP version \" <<" + CR); + sb.append(indent + tab + tab + tab + "version.toString() << \".\";" + CR); + sb.append(indent + tab + tab + "throw ProtocolVersionException(ss.str());" + CR); + sb.append(indent + tab + "}" + CR); + sb.append(indent + "}" + CR); + return sb.toString(); + } + + protected String generateConstantDeclarationException(String name, String methodReturnType, + int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + StringBuffer sb = new StringBuffer(); + sb.append(indent + "std::stringstream ss;" + CR); + sb.append(indent + "ss << \"Constant \\\"" + name + "\\\" cannot be converted to type " + + methodReturnType + " for AMQP version \" <<" + CR); + sb.append(indent + tab + "version.toString() << \".\";" + CR); + sb.append(indent + "throw ProtocolVersionException(ss.str());" + CR); + return sb.toString(); + } + + // Methods used for generation of code snippets for Server/ClientOperations class generation + + protected String generateOpsMethodHandlerGetMethods(AmqpModel model, boolean serverFlag, int indentSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + for (String thisClassName : model.getClassMap().keySet()) + { + AmqpClass thisClass = model.getClassMap().get(thisClassName); + // Only generate for this class if there is at least one method of the + // required chassis (server/client flag). + boolean chassisFoundFlag = false; + for (String thisMethodName : thisClass.getMethodMap().keySet()) + { + AmqpMethod method = thisClass.getMethodMap().get(thisMethodName); + boolean clientChassisFlag = method.getClientMethodFlagMap().isSet(); + boolean serverChassisFlag = method.getServerMethodFlagMap().isSet(); + if ((serverFlag && serverChassisFlag) || (!serverFlag && clientChassisFlag)) + { + chassisFoundFlag = true; + } + } + if (chassisFoundFlag) + { + sb.append(indent + "virtual AMQP_" + (serverFlag ? "Server" : "Client") + "Operations::" + + thisClass.getName() + "Handler* get" + thisClass.getName() + "Handler() = 0;" + CR); + } + } + return sb.toString(); + } + + protected String generateOpsInnerClasses(AmqpModel model, boolean serverFlag, int indentSize, int tabSize) + { + + String proxyClassName = "AMQP_" + (serverFlag ? "Server" : "Client") + "Proxy"; + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + StringBuffer sb = new StringBuffer(); + boolean first = true; + for (String thisClassName : model.getClassMap().keySet()) + { + AmqpClass thisClass = model.getClassMap().get(thisClassName); + String handlerClassName = thisClass.getName() + "Handler"; + if (!first) + { + sb.append(CR); + } + sb.append(indent + "// ==================== class " + handlerClassName + + " ====================" + CR); + sb.append(indent + "class " + handlerClassName); + if (thisClass.getVersionSet().size() != getVersionSet().size()) + { + sb.append(" // AMQP Version(s) " + thisClass.getVersionSet() + CR); + } + else + { + sb.append(CR); + } + sb.append(indent + "{" + CR); + sb.append(indent + "private:" + CR); + sb.append(indent + tab + proxyClassName + "* parent;" + CR); + sb.append(CR); + sb.append(indent + tab + "// Constructors and destructors" + CR); + sb.append(CR); + sb.append(indent + "protected:" + CR); + sb.append(indent + tab + handlerClassName + "() {}" + CR); + sb.append(indent + "public:" + CR); + sb.append(indent + tab + handlerClassName + + "(" + proxyClassName + "* _parent) {parent = _parent;}" + CR); + sb.append(indent + tab + "virtual ~" + handlerClassName + "() {}" + CR); + sb.append(CR); + sb.append(indent + tab + "// Protocol methods" + CR); + sb.append(CR); + sb.append(generateInnerClassMethods(thisClass, serverFlag, true, indentSize + tabSize, tabSize)); + sb.append(indent + "}; // class " + handlerClassName + CR); + first = false; + } + return sb.toString(); + } + + protected String generateInnerClassMethods(AmqpClass thisClass, boolean serverFlag, + boolean abstractMethodFlag, int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + String outerClassName = "AMQP_" + (serverFlag ? "Server" : "Client") + (abstractMethodFlag ? "Operations" + : "Proxy"); + boolean first = true; + for (String thisMethodName : thisClass.getMethodMap().keySet()) + { + AmqpMethod method = thisClass.getMethodMap().get(thisMethodName); + boolean clientChassisFlag = method.getClientMethodFlagMap().isSet(); + boolean serverChassisFlag = method.getServerMethodFlagMap().isSet(); + if ((serverFlag && serverChassisFlag) || (!serverFlag && clientChassisFlag)) + { + String methodName = parseForReservedWords(method.getName(), outerClassName + "." + thisClass.getName()); + AmqpOverloadedParameterMap overloadededParameterMap = + method.getOverloadedParameterLists(thisClass.getVersionSet(), this); + for (AmqpOrdinalFieldMap thisFieldMap : overloadededParameterMap.keySet()) + { + AmqpVersionSet versionSet = overloadededParameterMap.get(thisFieldMap); + if (!first) + { + sb.append(CR); + } + sb.append(indent + "virtual void " + methodName + "( u_int16_t channel"); + sb.append(generateMethodParameterList(thisFieldMap, indentSize + (5 * tabSize), true, true, true)); + sb.append(" )"); + if (abstractMethodFlag) + { + sb.append(" = 0"); + } + sb.append(";"); + if (versionSet.size() != getVersionSet().size()) + { + sb.append(" // AMQP Version(s) " + versionSet); + } + sb.append(CR); + first = false; + } + } + } + return sb.toString(); + } + + // Methods used for generation of code snippets for Server/ClientProxy class generation + + protected String generateHandlerPointerDefinitions(AmqpModel model, boolean serverFlag, + int indentSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + String outerClassName = "AMQP_" + (serverFlag ? "Server" : "Client") + "Operations"; + for (String thisClassName : model.getClassMap().keySet()) + { + AmqpClass thisClass = model.getClassMap().get(thisClassName); + sb.append(indent + outerClassName + "::" + thisClass.getName() + "Handler* " + + thisClass.getName() + "HandlerPtr;" + CR); + } + return sb.toString(); + } + + protected String generateHandlerPointerGetMethods(AmqpModel model, boolean serverFlag, + int indentSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + String outerClassName = "AMQP_" + (serverFlag ? "Server" : "Client") + "Operations"; + for (String thisClassName : model.getClassMap().keySet()) + { + AmqpClass thisClass = model.getClassMap().get(thisClassName); + sb.append(indent + "virtual inline " + outerClassName + "::" + thisClass.getName() + "Handler* get" + + thisClass.getName() + "Handler() { return &" + Utils.firstLower(thisClass.getName()) + ";}" + CR); + } + return sb.toString(); + } + + protected String generateProxyInnerClassInstances(AmqpModel model, boolean serverFlag, + int indentSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + String outerClassName = "AMQP_" + (serverFlag ? "Server" : "Client") + "Proxy"; + for (String thisClassName : model.getClassMap().keySet()) + { + AmqpClass thisClass = model.getClassMap().get(thisClassName); + String instanceName = parseForReservedWords(Utils.firstLower(thisClass.getName()), outerClassName); + String className = parseForReservedWords(thisClass.getName(), null); + sb.append(indent + className + " " + instanceName + ";"); + if (thisClass.getVersionSet().size() != getVersionSet().size()) + { + sb.append(" // AMQP Version(s) " + thisClass.getVersionSet() + CR); + } + else + { + sb.append(CR); + } + } + return sb.toString(); + } + + protected String generateProxyInnerClassGetMethodDecls(AmqpModel model, boolean serverFlag, + int indentSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + String outerClassName = "AMQP_" + (serverFlag ? "Server" : "Client") + "Proxy"; + for (String thisClassName : model.getClassMap().keySet()) + { + AmqpClass thisClass = model.getClassMap().get(thisClassName); + String className = parseForReservedWords(thisClass.getName(), outerClassName); + sb.append(indent + className + "& get" + className + "();"); + if (thisClass.getVersionSet().size() != getVersionSet().size()) + { + sb.append(" // AMQP Version(s) " + thisClass.getVersionSet() + CR); + } + else + { + sb.append(CR); + } + } + return sb.toString(); + } + + protected String generateProxyInnerClassDefinitions(AmqpModel model, boolean serverFlag, + int indentSize, int tabSize) + { + String proxyClassName = "AMQP_" + (serverFlag ? "Server" : "Client") + "Proxy"; + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + StringBuffer sb = new StringBuffer(); + boolean first = true; + for (String thisClassName : model.getClassMap().keySet()) + { + AmqpClass thisClass = model.getClassMap().get(thisClassName); + String className = thisClass.getName(); + String superclassName = "AMQP_" + (serverFlag ? "Server" : "Client") + "Operations::" + + thisClass.getName() + "Handler"; + if (!first) + { + sb.append(CR); + } + sb.append(indent + "// ==================== class " + className + + " ====================" + CR); + sb.append(indent + "class " + className + " : virtual public " + superclassName); + if (thisClass.getVersionSet().size() != getVersionSet().size()) + { + sb.append(" // AMQP Version(s) " + thisClass.getVersionSet() + CR); + } + else + { + sb.append(CR); + } + sb.append(indent + "{" + CR); + sb.append(indent + "private:" + CR); + sb.append(indent + tab + "OutputHandler* out;" + CR); + sb.append(indent + tab + proxyClassName + "* parent;" + CR); + sb.append(CR); + sb.append(indent + "public:" + CR); + sb.append(indent + tab + "// Constructors and destructors" + CR); + sb.append(CR); + sb.append(indent + tab + className + "(OutputHandler* out, " + proxyClassName + "* _parent) : " + CR); + sb.append(indent + tab + tab + "out(out) {parent = _parent;}" + CR); + sb.append(indent + tab + "virtual ~" + className + "() {}" + CR); + sb.append(CR); + sb.append(indent + tab + "// Protocol methods" + CR); + sb.append(CR); + sb.append(generateInnerClassMethods(thisClass, serverFlag, false, indentSize + tabSize, tabSize)); + sb.append(indent + "}; // class " + className + CR); + first = false; + } + return sb.toString(); + } + + protected String generateProxyConstructorInitializers(AmqpModel model, boolean serverFlag, + int indentSize) + { + String outerClassName = "AMQP_" + (serverFlag ? "Server" : "Client") + "Proxy"; + String superclassName = "AMQP_" + (serverFlag ? "Server" : "Client") + "Operations"; + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(indent + superclassName + "(major, minor)," + CR); + sb.append(indent + "version(major, minor)," + CR); + sb.append(indent + "out(out)"); + Iterator<String> cItr = model.getClassMap().keySet().iterator(); + while (cItr.hasNext()) + { + AmqpClass thisClass = model.getClassMap().get(cItr.next()); + String instanceName = parseForReservedWords(Utils.firstLower(thisClass.getName()), outerClassName); + sb.append("," + CR); + sb.append(indent + instanceName + "(out, this)"); + if (!cItr.hasNext()) + { + sb.append(CR); + } + } + return sb.toString(); + } + + protected String generateProxyInnerClassGetMethodImpls(AmqpModel model, boolean serverFlag, + int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + StringBuffer sb = new StringBuffer(); + String outerClassName = "AMQP_" + (serverFlag ? "Server" : "Client") + "Proxy"; + Iterator<String> cItr = model.getClassMap().keySet().iterator(); + while (cItr.hasNext()) + { + AmqpClass thisClass = model.getClassMap().get(cItr.next()); + String className = thisClass.getName(); + String instanceName = parseForReservedWords(Utils.firstLower(thisClass.getName()), outerClassName); + sb.append(indent + outerClassName + "::" + className + "& " + + outerClassName + "::get" + className + "()" + CR); + sb.append(indent + "{" + CR); + if (thisClass.getVersionSet().size() != getVersionSet().size()) + { + sb.append(indent + tab + "if (!" + generateVersionCheck(thisClass.getVersionSet()) + ")" + CR); + sb.append(indent + tab + tab + "throw new ProtocolVersionException();" + CR); + } + sb.append(indent + tab + "return " + instanceName + ";" + CR); + sb.append(indent + "}" + CR); + if (cItr.hasNext()) + { + sb.append(CR); + } + } + return sb.toString(); + } + + protected String generateProxyInnerClassImpl(AmqpModel model, boolean serverFlag, + int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + boolean firstClassFlag = true; + for (String thisClassName : model.getClassMap().keySet()) + { + AmqpClass thisClass = model.getClassMap().get(thisClassName); + String className = thisClass.getName(); + if (!firstClassFlag) + { + sb.append(CR); + } + sb.append(indent + "// ==================== class " + className + + " ====================" + CR); + sb.append(generateInnerClassMethodImpls(thisClass, serverFlag, indentSize, tabSize)); + firstClassFlag = false; + } + return sb.toString(); + } + + protected String generateInnerClassMethodImpls(AmqpClass thisClass, boolean serverFlag, + int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + String outerclassName = "AMQP_" + (serverFlag ? "Server" : "Client") + "Proxy"; + boolean first = true; + for (String thisMethodName : thisClass.getMethodMap().keySet()) + { + AmqpMethod method = thisClass.getMethodMap().get(thisMethodName); + String methodBodyClassName = thisClass.getName() + Utils.firstUpper(method.getName()) + "Body"; + boolean clientChassisFlag = method.getClientMethodFlagMap().isSet(); + boolean serverChassisFlag = method.getServerMethodFlagMap().isSet(); + boolean versionConsistentFlag = method.isVersionConsistent(getVersionSet()); + if ((serverFlag && serverChassisFlag) || (!serverFlag && clientChassisFlag)) + { + String methodName = parseForReservedWords(method.getName(), outerclassName + "." + thisClass.getName()); + AmqpOverloadedParameterMap overloadededParameterMap = + method.getOverloadedParameterLists(thisClass.getVersionSet(), this); + for (AmqpOrdinalFieldMap thisFieldMap : overloadededParameterMap.keySet()) + { + AmqpVersionSet versionSet = overloadededParameterMap.get(thisFieldMap); + if (!first) + { + sb.append(CR); + } + sb.append(indent + "void " + outerclassName + "::" + thisClass.getName() + "::" + + methodName + "( u_int16_t channel"); + sb.append(generateMethodParameterList(thisFieldMap, indentSize + (5 * tabSize), true, true, true)); + sb.append(" )"); + if (versionSet.size() != getVersionSet().size()) + { + sb.append(" // AMQP Version(s) " + versionSet); + } + sb.append(CR); + sb.append(indent + "{" + CR); + sb.append(generateMethodBodyCallContext(thisFieldMap, outerclassName, methodBodyClassName, + versionConsistentFlag, versionSet, indentSize + tabSize, tabSize)); + sb.append(indent + "}" + CR); + sb.append(CR); + first = false; + } + } + } + return sb.toString(); + } + + protected String generateMethodBodyCallContext(AmqpOrdinalFieldMap fieldMap, String outerclassName, + String methodBodyClassName, boolean versionConsistentFlag, AmqpVersionSet versionSet, + int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + StringBuffer sb = new StringBuffer(); + if (versionConsistentFlag) + { + sb.append(generateMethodBodyCall(fieldMap, methodBodyClassName, null, indentSize, tabSize)); + } + else + { + boolean firstOverloadedMethodFlag = true; + for (AmqpVersion thisVersion : versionSet) + { + sb.append(indent); + if (!firstOverloadedMethodFlag) + { + sb.append("else "); + } + sb.append("if (" + generateVersionCheck(thisVersion) + ")" + CR); + sb.append(indent + "{" + CR); + sb.append(generateMethodBodyCall(fieldMap, methodBodyClassName, thisVersion, + indentSize + tabSize, tabSize)); + sb.append(indent + "}" + CR); + firstOverloadedMethodFlag = false; + } + sb.append(indent + "else" + CR); + sb.append(indent + "{" + CR); + sb.append(indent + tab + "std::stringstream ss;" + CR); + sb.append(indent + tab + "ss << \"Call to " + outerclassName + "::" + methodBodyClassName + + "(u_int16_t" + generateMethodParameterList(fieldMap, 0, true, true, false) + ")\"" + CR); + sb.append(indent + tab + tab + "<< \" is invalid for AMQP version \" << version.toString() << \".\";" + CR); + sb.append(indent + tab + "throw new ProtocolVersionException(ss.str());" + CR); + sb.append(indent + "}" + CR); + } + return sb.toString(); + } + + protected String generateMethodBodyCall(AmqpOrdinalFieldMap fieldMap, String methodBodyClassName, + AmqpVersion version, int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + String namespace = version != null ? version.namespace() + "::" : ""; + StringBuffer sb = new StringBuffer(indent + "out->send( new AMQFrame(parent->getProtocolVersion(), channel," + CR); + sb.append(indent + tab + "new " + namespace + methodBodyClassName + "( parent->getProtocolVersion()"); + sb.append(generateMethodParameterList(fieldMap, indentSize + (5 * tabSize), true, false, true)); + sb.append(" )));" + CR); + return sb.toString(); + } + + protected String generateMethodBodyIncludes(AmqpClass thisClass, int indentSize) + { + StringBuffer sb = new StringBuffer(); + if (thisClass != null) + { + sb.append(generateClassMethodBodyInclude(thisClass, indentSize)); + } + else + { + for (String thisClassName : getModel().getClassMap().keySet()) + { + thisClass = getModel().getClassMap().get(thisClassName); + sb.append(generateClassMethodBodyInclude(thisClass, indentSize)); + } + } + return sb.toString(); + } + + protected String generateClassMethodBodyInclude(AmqpClass thisClass, int indentSize) + { + StringBuffer sb = new StringBuffer(); + String indent = Utils.createSpaces(indentSize); + for (String thisMethodName : thisClass.getMethodMap().keySet()) + { + AmqpMethod method = thisClass.getMethodMap().get(thisMethodName); + sb.append(indent + "#include <" + thisClass.getName() + + Utils.firstUpper(method.getName()) + "Body.h>" + CR); + } + return sb.toString(); + } + + // Methods used for generation of code snippets for MethodBody class generation + + protected String getIndex(AmqpOrdinalVersionMap indexMap, AmqpVersion version) + { + for (Integer thisIndex : indexMap.keySet()) + { + AmqpVersionSet versionSet = indexMap.get(thisIndex); + if (versionSet.contains(version)) + { + return String.valueOf(thisIndex); + } + } + throw new AmqpTemplateException("Unable to find index for version " + version); + } + + protected String generateFieldDeclarations(AmqpFieldMap fieldMap, AmqpVersion version, int indentSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + + if (version == null) + { + version = getVersionSet().first(); + } + AmqpOrdinalFieldMap ordinalFieldMap = fieldMap.getMapForVersion(version, true, this); + for (Integer thisOrdinal : ordinalFieldMap.keySet()) + { + String[] fieldDomainPair = ordinalFieldMap.get(thisOrdinal); + sb.append(indent + fieldDomainPair[FIELD_CODE_TYPE] + " " + fieldDomainPair[FIELD_NAME] + ";" + CR); + } + return sb.toString(); + } + + protected String generateFieldGetMethods(AmqpFieldMap fieldMap, AmqpVersion version, int indentSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + + if (version == null) + { + version = getVersionSet().first(); + } + AmqpOrdinalFieldMap ordinalFieldMap = fieldMap.getMapForVersion(version, true, this); + for (Integer thisOrdinal : ordinalFieldMap.keySet()) + { + String[] fieldDomainPair = ordinalFieldMap.get(thisOrdinal); + sb.append(indent + "inline " + setRef(fieldDomainPair[FIELD_CODE_TYPE]) + " get" + + Utils.firstUpper(fieldDomainPair[FIELD_NAME]) + "() { return " + + fieldDomainPair[FIELD_NAME] + "; }" + CR); + } + return sb.toString(); + } + + protected String generatePrintMethodContents(AmqpFieldMap fieldMap, AmqpVersion version, int indentSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + + if (version == null) + { + version = getVersionSet().first(); + } + AmqpOrdinalFieldMap ordinalFieldMap = fieldMap.getMapForVersion(version, true, this); + boolean firstFlag = true; + for (Integer thisOrdinal : ordinalFieldMap.keySet()) + { + String[] fieldDomainPair = ordinalFieldMap.get(thisOrdinal); + String cast = fieldDomainPair[FIELD_CODE_TYPE].compareTo("u_int8_t") == 0 ? "(int)" : ""; + sb.append(indent + "out << \""); + if (!firstFlag) + { + sb.append("; "); + } + sb.append(fieldDomainPair[FIELD_NAME] + "=\" << " + cast + fieldDomainPair[FIELD_NAME] + ";" + CR); + firstFlag = false; + } + return sb.toString(); + } + + protected String generateBodySizeMethodContents(AmqpFieldMap fieldMap, AmqpVersion version, + int indentSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + ArrayList<String> bitFieldList = new ArrayList<String>(); + AmqpOrdinalFieldMap ordinalFieldMap = fieldMap.getMapForVersion(version, false, this); + Iterator<Integer> oItr = ordinalFieldMap.keySet().iterator(); + int ordinal = 0; + while (oItr.hasNext()) + { + ordinal = oItr.next(); + String[] fieldDomainPair = ordinalFieldMap.get(ordinal); + AmqpVersion thisVersion = version == null ? getVersionSet().first() : version; + String domainType = getDomainType(fieldDomainPair[FIELD_CODE_TYPE], thisVersion); + + // Defer bit types by adding them to an array. When the first subsequent non-bit + // type is encountered, then handle the bits. This allows consecutive bits to be + // placed into the same byte(s) - 8 bits to the byte. + if (domainType.compareTo("bit") == 0) + { + bitFieldList.add(fieldDomainPair[FIELD_NAME]); + } + else + { + if (bitFieldList.size() > 0) // Handle accumulated bit types (if any) + { + sb.append(generateBitArrayBodySizeMethodContents(bitFieldList, ordinal, indentSize)); + } + sb.append(indent + "size += " + + typeMap.get(domainType).size.replaceAll("#", fieldDomainPair[FIELD_NAME]) + + "; /* " + fieldDomainPair[FIELD_NAME] + ": " + + domainType + " */" + CR); + } + } + if (bitFieldList.size() > 0) // Handle any remaining accumulated bit types + { + sb.append(generateBitArrayBodySizeMethodContents(bitFieldList, ordinal, indentSize)); + } + return sb.toString(); + } + + protected String generateBitArrayBodySizeMethodContents(ArrayList<String> bitFieldList, + int ordinal, int indentSize) + { + int numBytes = ((bitFieldList.size() - 1) / 8) + 1; + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + String comment = bitFieldList.size() == 1 ? + bitFieldList.get(0) + ": bit" : + "Combinded bits: " + bitFieldList; + sb.append(indent + "size += " + + typeMap.get("bit").size.replaceAll("~", String.valueOf(numBytes)) + + "; /* " + comment + " */" + CR); + bitFieldList.clear(); + return sb.toString(); + } + + protected String generateEncodeMethodContents(AmqpFieldMap fieldMap, AmqpVersion version, + int indentSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + ArrayList<String> bitFieldList = new ArrayList<String>(); + AmqpOrdinalFieldMap ordinalFieldMap = fieldMap.getMapForVersion(version, false, this); + Iterator<Integer> oItr = ordinalFieldMap.keySet().iterator(); + int ordinal = 0; + while (oItr.hasNext()) + { + ordinal = oItr.next(); + String[] fieldDomainPair = ordinalFieldMap.get(ordinal); + AmqpVersion thisVersion = version == null ? getVersionSet().first() : version; + String domainType = getDomainType(fieldDomainPair[FIELD_CODE_TYPE], thisVersion); + + // Defer bit types by adding them to an array. When the first subsequent non-bit + // type is encountered, then handle the bits. This allows consecutive bits to be + // placed into the same byte(s) - 8 bits to the byte. + if (domainType.compareTo("bit") == 0) + { + bitFieldList.add(fieldDomainPair[FIELD_NAME]); + } + else + { + if (bitFieldList.size() > 0) // Handle accumulated bit types (if any) + { + sb.append(generateBitEncodeMethodContents(bitFieldList, ordinal, indentSize)); + } + sb.append(indent + + typeMap.get(domainType).encodeExpression.replaceAll("#", fieldDomainPair[FIELD_NAME]) + + "; /* " + fieldDomainPair[FIELD_NAME] + ": " + domainType + " */" + CR); + } + } + if (bitFieldList.size() > 0) // Handle any remaining accumulated bit types + { + sb.append(generateBitEncodeMethodContents(bitFieldList, ordinal, indentSize)); + } + + return sb.toString(); + } + + protected String generateBitEncodeMethodContents(ArrayList<String> bitFieldList, int ordinal, + int indentSize) + { + int numBytes = ((bitFieldList.size() - 1) / 8) + 1; + String indent = Utils.createSpaces(indentSize); + String bitArrayName = "flags_" + ordinal; + StringBuffer sb = new StringBuffer(indent + "u_int8_t " + bitArrayName + + "[" + numBytes + "] = {0};" + + (numBytes != 1 ? " /* All array elements will be initialized to 0 */" : "") + + CR); + for (int i = 0; i < bitFieldList.size(); i++) + { + int bitIndex = i % 8; + int byteIndex = i / 8; + sb.append(indent + bitArrayName + "[" + byteIndex + "] |= " + bitFieldList.get(i) + + " << " + bitIndex + "; /* " + bitFieldList.get(i) + ": bit */" + CR); + } + for (int i = 0; i < numBytes; i++) + { + sb.append(indent + "buffer.putOctet(" + bitArrayName + "[" + i + "]);" + CR); + } + bitFieldList.clear(); + return sb.toString(); + } + + protected String generateDecodeMethodContents(AmqpFieldMap fieldMap, AmqpVersion version, + int indentSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + ArrayList<String> bitFieldList = new ArrayList<String>(); + AmqpOrdinalFieldMap ordinalFieldMap = fieldMap.getMapForVersion(version, false, this); + Iterator<Integer> oItr = ordinalFieldMap.keySet().iterator(); + int ordinal = 0; + while (oItr.hasNext()) + { + ordinal = oItr.next(); + String[] fieldDomainPair = ordinalFieldMap.get(ordinal); + AmqpVersion thisVersion = version == null ? getVersionSet().first() : version; + String domainType = getDomainType(fieldDomainPair[FIELD_CODE_TYPE], thisVersion); + + // Defer bit types by adding them to an array. When the first subsequent non-bit + // type is encountered, then handle the bits. This allows consecutive bits to be + // placed into the same byte(s) - 8 bits to the byte. + if (domainType.compareTo("bit") == 0) + { + bitFieldList.add(fieldDomainPair[FIELD_NAME]); + } + else + { + if (bitFieldList.size() > 0) // Handle accumulated bit types (if any) + { + sb.append(generateBitDecodeMethodContents(bitFieldList, ordinal, indentSize)); + } + sb.append(indent + + typeMap.get(domainType).decodeExpression.replaceAll("#", fieldDomainPair[FIELD_NAME]) + + "; /* " + fieldDomainPair[FIELD_NAME] + ": " + domainType + " */" + CR); + } + } + if (bitFieldList.size() > 0) // Handle any remaining accumulated bit types + { + sb.append(generateBitDecodeMethodContents(bitFieldList, ordinal, indentSize)); + } + + return sb.toString(); + } + + protected String generateBitDecodeMethodContents(ArrayList<String> bitFieldList, int ordinal, + int indentSize) + { + int numBytes = ((bitFieldList.size() - 1) / 8) + 1; + String indent = Utils.createSpaces(indentSize); + String bitArrayName = "flags_" + ordinal; + StringBuffer sb = new StringBuffer(indent + "u_int8_t " + bitArrayName + + "[" + numBytes + "];" + CR); + for (int i = 0; i < numBytes; i++) + { + sb.append(indent + bitArrayName + "[" + i + "] = buffer.getOctet();" + CR); + } + for (int i = 0; i < bitFieldList.size(); i++) + { + int bitIndex = i % 8; + int byteIndex = i / 8; + sb.append(indent + bitFieldList.get(i) + " = (1 << " + bitIndex + ") & " + + bitArrayName + "[" + byteIndex + "]; /* " + bitFieldList.get(i) + + ": bit */" + CR); + } + bitFieldList.clear(); + return sb.toString(); + } + + protected String generateFieldList(AmqpFieldMap fieldMap, AmqpVersion version, boolean defineFlag, + boolean initializerFlag, int indentSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + AmqpOrdinalFieldMap ordinalFieldMap = fieldMap.getMapForVersion(version, true, this); + Iterator<Integer> oItr = ordinalFieldMap.keySet().iterator(); + while (oItr.hasNext()) + { + int ordinal = oItr.next(); + String[] fieldDomainPair = ordinalFieldMap.get(ordinal); + sb.append(indent + (defineFlag ? setRef(fieldDomainPair[FIELD_CODE_TYPE]) + " " : "") + + fieldDomainPair[FIELD_NAME] + (initializerFlag ? "(" + fieldDomainPair[FIELD_NAME] + ")" : "") + + (oItr.hasNext() ? "," : "") + CR); + } + return sb.toString(); + } + + protected String generateMethodParameterList(AmqpOrdinalFieldMap fieldMap, int indentSize, + boolean leadingCommaFlag, boolean fieldTypeFlag, boolean fieldNameFlag) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + boolean first = true; + Iterator<Integer> pItr = fieldMap.keySet().iterator(); + while (pItr.hasNext()) + { + String[] field = fieldMap.get(pItr.next()); + if (first && leadingCommaFlag) + { + sb.append("," + (fieldNameFlag ? CR : " ")); + } + if (!first || leadingCommaFlag) + { + sb.append(indent); + } + sb.append( + (fieldTypeFlag ? setRef(field[FIELD_CODE_TYPE]) : "") + + (fieldNameFlag ? " " + field[FIELD_NAME] : "") + + (pItr.hasNext() ? "," + (fieldNameFlag ? CR : " ") : "")); + first = false; + } + return sb.toString(); + } + + protected String generateConstructor(AmqpClass thisClass, AmqpMethod method, + AmqpVersion version, int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + StringBuffer sb = new StringBuffer(); + if (method.getFieldMap().size() > 0) + { + sb.append(indent + thisClass.getName() + Utils.firstUpper(method.getName()) + "Body(ProtocolVersion& version," + CR); + sb.append(generateFieldList(method.getFieldMap(), version, true, false, 8)); + sb.append(indent + tab + ") :" + CR); + sb.append(indent + tab + "AMQMethodBody(version)," + CR); + sb.append(generateFieldList(method.getFieldMap(), version, false, true, 8)); + sb.append(indent + "{ }" + CR); + } + return sb.toString(); + } + + protected String generateServerOperationsInvoke(AmqpClass thisClass, AmqpMethod method, + AmqpVersion version, int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + StringBuffer sb = new StringBuffer(); + + if (method.getServerMethodFlagMap().size() > 0) // At least one AMQP version defines this method as a server method + { + Iterator<Boolean> bItr = method.getServerMethodFlagMap().keySet().iterator(); + while (bItr.hasNext()) + { + if (bItr.next()) // This is a server operation + { + boolean fieldMapNotEmptyFlag = method.getFieldMap().size() > 0; + sb.append(indent + "inline void invoke(AMQP_ServerOperations& target, u_int16_t channel)" + CR); + sb.append(indent + "{" + CR); + sb.append(indent + tab + "target.get" + thisClass.getName() + "Handler()->" + + parseForReservedWords(Utils.firstLower(method.getName()), + thisClass.getName() + Utils.firstUpper(method.getName()) + "Body.invoke()") + "(channel"); + if (fieldMapNotEmptyFlag) + { + sb.append("," + CR); + sb.append(generateFieldList(method.getFieldMap(), version, false, false, indentSize + 4 * tabSize)); + sb.append(indent + tab + tab + tab + tab); + } + sb.append(");" + CR); + sb.append(indent + "}" + CR); + } + } + } + return sb.toString(); + } + + // Methods for generation of code snippets for amqp_methods.h/cpp files + + protected String generateMethodBodyIncludeList(AmqpModel model, int indentSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + + for (String thisClassName : model.getClassMap().keySet()) + { + AmqpClass thisClass = model.getClassMap().get(thisClassName); + for (String thisMethodName : thisClass.getMethodMap().keySet()) + { + AmqpMethod method = thisClass.getMethodMap().get(thisMethodName); + sb.append(indent + "#include \"" + thisClass.getName() + Utils.firstUpper(method.getName()) + "Body.h\"" + CR); + } + } + + return sb.toString(); + } + + protected String generateMethodBodyInstances(AmqpModel model, int indentSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + + for (String thisClassName : model.getClassMap().keySet()) + { + AmqpClass thisClass = model.getClassMap().get(thisClassName); + for (String thisMethodName : thisClass.getMethodMap().keySet()) + { + AmqpMethod method = thisClass.getMethodMap().get(thisMethodName); + sb.append(indent + "const " + thisClass.getName() + Utils.firstUpper(method.getName()) + "Body " + + Utils.firstLower(thisClass.getName()) + "_" + method.getName() + ";" + CR); + } + } + + return sb.toString(); + } + + protected String generateMethodBodyMapEntry(AmqpModel model, int indentSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + + for (AmqpVersion version : getVersionSet()) + { + for (String thisClassName : model.getClassMap().keySet()) + { + AmqpClass thisClass = model.getClassMap().get(thisClassName); + for (String thisMethodName : thisClass.getMethodMap().keySet()) + { + AmqpMethod method = thisClass.getMethodMap().get(thisMethodName); + String namespace = method.isVersionConsistent(getVersionSet()) ? "" : version.namespace() + "::"; + try + { + int classOrdinal = thisClass.getIndexMap().getOrdinal(version); + int methodOrdinal = method.getIndexMap().getOrdinal(version); + String methodModyClassName = namespace + thisClass.getName() + Utils.firstUpper(method.getName()) + "Body"; + sb.append(indent + "insert(std::make_pair(createMapKey(" + classOrdinal + ", " + + methodOrdinal + ", " + version.getMajor() + ", " + version.getMinor() + + "), &createMethodBodyFn<" + methodModyClassName + ">));" + CR); + } + catch (AmqpTypeMappingException e) + { + } // ignore + } + } + } + + return sb.toString(); + } + + // Helper functions + + private String generateVersionCheck(AmqpVersion version) + { + return "version.equals(" + version.getMajor() + ", " + version.getMinor() + ")"; + } + + private String generateVersionCheck(AmqpVersionSet versionSet) + { + StringBuffer sb = new StringBuffer(); + for (AmqpVersion v : versionSet) + { + if (!v.equals(versionSet.first())) + { + sb.append(" || "); + } + if (versionSet.size() > 1) + { + sb.append("("); + } + sb.append("version.equals(" + v.getMajor() + ", " + v.getMinor() + ")"); + if (versionSet.size() > 1) + { + sb.append(")"); + } + } + return sb.toString(); + } + + private String parseForReservedWords(String name, String context) + { + for (String cppReservedWord : cppReservedWords) + { + if (name.compareTo(cppReservedWord) == 0) + { + if (!quietFlag) + { + System.out.println("WARNING: " + (context == null ? "" : context + ": ") + + "Found XML method \"" + name + "\", which is a C++ reserved word. " + + "Changing generated name to \"" + name + "_\"."); + } + return name + "_"; + } + } + + for (String cppCommonDefine : cppCommonDefines) + { + if (name.compareTo(cppCommonDefine) == 0) + { + if (!quietFlag) + { + System.out.println("WARNING: " + (context == null ? "" : context + ": ") + + "Found XML method \"" + name + "\", which may clash with commonly used defines within C++. " + + "Changing generated name to \"" + name + "_\"."); + } + return name + "_"; + } + } + + return name; + } + + private String setRef(String codeType) + { + if (codeType.compareTo("string") == 0 || + codeType.compareTo("FieldTable") == 0) + { + return "const " + codeType + "&"; + } + return codeType; + } + + private String camelCaseName(String name, boolean upperFirstFlag) + { + StringBuffer ccn = new StringBuffer(); + String[] toks = name.split("[-_.\\ ]"); + for (int i = 0; i < toks.length; i++) + { + StringBuffer b = new StringBuffer(toks[i]); + if (upperFirstFlag || i > 0) + { + b.setCharAt(0, Character.toUpperCase(toks[i].charAt(0))); + } + ccn.append(b); + } + return ccn.toString(); + } + + public static Factory<CppGenerator> _factoryInstance = new Factory<CppGenerator>() + { + + public CppGenerator newInstance() + { + return new CppGenerator(); + } + }; + + public static Factory<CppGenerator> getFactory() + { + return _factoryInstance; + } + + void processModelTemplate(NamedTemplate template, AmqpVersion version) + { + //To change body of implemented methods use File | Settings | File Templates. + } + public String getNativeType(String type) + { + throw new UnsupportedOperationException(); + } + + public String getEncodingType(String type) + { + throw new UnsupportedOperationException(); + } + +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/DotnetGenerator.java b/qpid/gentools/src/org/apache/qpid/gentools/DotnetGenerator.java new file mode 100644 index 0000000000..9fc81dd428 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/DotnetGenerator.java @@ -0,0 +1,382 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.gentools; + +import java.io.File; +import java.util.TreeMap; + +public class DotnetGenerator extends Generator +{ + private class DomainInfo + { + public String type; + public String size; + public String encodeExpression; + public String decodeExpression; + + public DomainInfo(String domain, String size, String encodeExpression, String decodeExpression) + { + this.type = domain; + this.size = size; + this.encodeExpression = encodeExpression; + this.decodeExpression = decodeExpression; + } + } + + private static TreeMap<String, DomainInfo> typeMap = new TreeMap<String, DomainInfo>(); + + public String getNativeType(String type) + { + throw new UnsupportedOperationException(); + } + + public String getEncodingType(String type) + { + throw new UnsupportedOperationException(); + } + + public DotnetGenerator() + { + super(); + // Load .NET type and size maps. + // Adjust or add to these lists as new types are added/defined. + // The char '#' will be replaced by the field variable name (any type). + // The char '~' will be replaced by the compacted bit array size (type bit only). + // TODO: I have left a copy of the Java typeMap here - replace with appropriate .NET values. + typeMap.put("bit", new DomainInfo( + "boolean", // .NET code type + "~", // size + "EncodingUtils.writeBooleans(buffer, #)", // encode expression + "# = EncodingUtils.readBooleans(buffer)")); // decode expression + typeMap.put("content", new DomainInfo( + "Content", // .NET code type + "EncodingUtils.encodedContentLength(#)", // size + "EncodingUtils.writeContentBytes(buffer, #)", // encode expression + "# = EncodingUtils.readContent(buffer)")); // decode expression + typeMap.put("long", new DomainInfo( + "long", // .NET code type + "4", // size + "EncodingUtils.writeUnsignedInteger(buffer, #)", // encode expression + "# = buffer.getUnsignedInt()")); // decode expression + typeMap.put("longlong", new DomainInfo( + "long", // .NET code type + "8", // size + "buffer.putLong(#)", // encode expression + "# = buffer.getLong()")); // decode expression + typeMap.put("longstr", new DomainInfo( + "byte[]", // .NET code type + "EncodingUtils.encodedLongstrLength(#)", // size + "EncodingUtils.writeLongStringBytes(buffer, #)", // encode expression + "# = EncodingUtils.readLongstr(buffer)")); // decode expression + typeMap.put("octet", new DomainInfo( + "short", // .NET code type + "1", // size + "EncodingUtils.writeUnsignedByte(buffer, #)", // encode expression + "# = buffer.getUnsigned()")); // decode expression + typeMap.put("short", new DomainInfo( + "int", // .NET code type + "2", // size + "EncodingUtils.writeUnsignedShort(buffer, #)", // encode expression + "# = buffer.getUnsignedShort()")); // decode expression + typeMap.put("shortstr", new DomainInfo( + "AMQShortString", // .NET code type + "EncodingUtils.encodedShortStringLength(#)", // size + "EncodingUtils.writeShortStringBytes(buffer, #)", // encode expression + "# = EncodingUtils.readAMQShortString(buffer)")); // decode expression + typeMap.put("table", new DomainInfo( + "FieldTable", // .NET code type + "EncodingUtils.encodedFieldTableLength(#)", // size + "EncodingUtils.writeFieldTableBytes(buffer, #)", // encode expression + "# = EncodingUtils.readFieldTable(buffer)")); // decode expression + typeMap.put("timestamp", new DomainInfo( + "long", // .NET code type + "8", // size + "EncodingUtils.writeTimestamp(buffer, #)", // encode expression + "# = EncodingUtils.readTimestamp(buffer)")); // decode expression + } + + void processModelTemplate(NamedTemplate template, AmqpVersion version) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + void processClassTemplate(NamedTemplate template, AmqpClass amqpClass, AmqpVersion version) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + void processMethodTemplate(NamedTemplate template, AmqpClass amqpClass, AmqpMethod amqpMethod, AmqpVersion version) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + void processFieldTemplate(NamedTemplate template, AmqpClass amqpClass, AmqpMethod amqpMethod, AmqpField amqpField, AmqpVersion version) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + protected String prepareFilename(String filenameTemplate, + AmqpClass thisClass, AmqpMethod method, AmqpField field, AmqpVersion version) + { + StringBuffer sb = new StringBuffer(filenameTemplate); + if (thisClass != null) + { + replaceToken(sb, "${CLASS}", thisClass.getName()); + } + if (method != null) + { + replaceToken(sb, "${METHOD}", method.getName()); + } + if (field != null) + { + replaceToken(sb, "${FIELD}", field.getName()); + } + return sb.toString(); + } + + @Override + protected void processClassList(StringBuffer sb, int listMarkerStartIndex, + int listMarkerEndIndex, AmqpModel model, AmqpVersion version) + throws AmqpTemplateException, AmqpTypeMappingException + { + String codeSnippet; + int lend = sb.indexOf(CR, listMarkerStartIndex) + 1; // Include cr at end of line + String tline = sb.substring(listMarkerEndIndex, lend); // Line excluding line marker, including cr + int tokStart = tline.indexOf('$'); + String token = tline.substring(tokStart).trim(); + sb.delete(listMarkerStartIndex, lend); + + // TODO: Add in tokens and calls to their corresponding generator methods here... + if (token.compareTo("${??????????}") == 0) + { + codeSnippet = token; // This is a stub to get the compile working - remove when gen method is present. +// codeSnippet = generateRegistry(model, 8, 4); + } + + else // Oops! + { + throw new AmqpTemplateException("Template token " + token + " unknown."); + } + sb.insert(listMarkerStartIndex, codeSnippet); + } + + @Override + protected void processConstantList(StringBuffer sb, + int listMarkerStartIndex, int listMarkerEndIndex, + AmqpConstantSet constantSet) throws AmqpTemplateException, + AmqpTypeMappingException + { + String codeSnippet; + int lend = sb.indexOf(CR, listMarkerStartIndex) + 1; // Include cr at end of line + String tline = sb.substring(listMarkerEndIndex, lend); // Line excluding line marker, including cr + int tokStart = tline.indexOf('$'); + String token = tline.substring(tokStart).trim(); + sb.delete(listMarkerStartIndex, lend); + + // TODO: Add in tokens and calls to their corresponding generator methods here... + if (token.compareTo("${??????????}") == 0) + { + codeSnippet = token; // This is a stub to get the compile working - remove when gen method is present. +// codeSnippet = generateConstantGetMethods(constantSet, 4, 4); + } + + else // Oops! + { + throw new AmqpTemplateException("Template token " + token + " unknown."); + } + sb.insert(listMarkerStartIndex, codeSnippet); + } + + @Override + protected void processFieldList(StringBuffer sb, int listMarkerStartIndex, + int listMarkerEndIndex, AmqpFieldMap fieldMap, AmqpVersion version) + { + String codeSnippet; + int lend = sb.indexOf(CR, listMarkerStartIndex) + 1; // Include cr at end of line + String tline = sb.substring(listMarkerEndIndex, lend); // Line excluding line marker, including cr + int tokStart = tline.indexOf('$'); + String token = tline.substring(tokStart).trim(); + sb.delete(listMarkerStartIndex, lend); + + // TODO: Add in tokens and calls to their corresponding generator methods here... + if (token.compareTo("${??????????}") == 0) + { + codeSnippet = token; // This is a stub to get the compile working - remove when gen method is present. +// codeSnippet = fieldMap.parseFieldMap(declarationGenerateMethod, +// mangledDeclarationGenerateMethod, 4, 4, this); + } + + else // Oops! + { + throw new AmqpTemplateException("Template token " + token + " unknown."); + } + sb.insert(listMarkerStartIndex, codeSnippet); + } + + @Override + protected void processMethodList(StringBuffer sb, int listMarkerStartIndex, + int listMarkerEndIndex, AmqpClass thisClass) + throws AmqpTemplateException, AmqpTypeMappingException + { + String codeSnippet; + int lend = sb.indexOf(CR, listMarkerStartIndex) + 1; // Include cr at end of line + String tline = sb.substring(listMarkerEndIndex, lend); // Line excluding line marker, including cr + int tokStart = tline.indexOf('$'); + String token = tline.substring(tokStart).trim(); + sb.delete(listMarkerStartIndex, lend); + + // TODO: Add in tokens and calls to their corresponding generator methods here... + if (token.compareTo("${??????????}") == 0) + { + codeSnippet = token; // This is a stub to get the compile working - remove when gen method is present. + } + + else // Oops! + { + throw new AmqpTemplateException("Template token " + token + " unknown."); + } + sb.insert(listMarkerStartIndex, codeSnippet); + } + + @Override + protected void processModelTemplate(NamedTemplate template) + { + // I've put in the Java model here - this can be changed if a different pattern is required. + processTemplate(template, null, null, null, null); + } + + @Override + protected void processClassTemplate(NamedTemplate template, AmqpClass thisClass) + { + // I've put in the Java model here - this can be changed if a different pattern is required. + processTemplate(template, thisClass, null, null, null); + } + + @Override + protected void processMethodTemplate(NamedTemplate template, AmqpClass thisClass, + AmqpMethod method) + { + // I've put in the Java model here - this can be changed if a different pattern is required. + processTemplate(template, thisClass, method, null, null); + } + + @Override + protected void processTemplate(NamedTemplate template, AmqpClass thisClass, + AmqpMethod method, AmqpField field, AmqpVersion version) + { + // I've put in the Java model here - this can be changed if a different pattern is required. + StringBuffer sb = new StringBuffer(template.getTemplate()); + String filename = prepareFilename(getTemplateFileName(sb), thisClass, method, field, version); + try + { + processAllLists(sb, thisClass, method, null); + } + catch (AmqpTemplateException e) + { + System.out.println("WARNING: " + template.getName() + ": " + e.getMessage()); + } + try + { + processAllTokens(sb, thisClass, method, field, null); + } + catch (AmqpTemplateException e) + { + System.out.println("WARNING: " + template.getName() + ": " + e.getMessage()); + } + writeTargetFile(sb, new File(getOutputDirectory() + Utils.FILE_SEPARATOR + filename)); + generatedFileCounter++; + } + + @Override + protected String processToken(String token, AmqpClass thisClass, + AmqpMethod method, AmqpField field, AmqpVersion version) + throws AmqpTemplateException, AmqpTypeMappingException + { + // TODO Auto-generated method stub + return null; + } + + public String getGeneratedType(String domainName, AmqpVersion version) + throws AmqpTypeMappingException + { + String domainType = getDomainType(domainName, version); + if (domainType == null) + { + throw new AmqpTypeMappingException("Domain type \"" + domainName + + "\" not found in Java typemap."); + } + DomainInfo info = typeMap.get(domainType); + if (info == null) + { + throw new AmqpTypeMappingException("Unknown domain: \"" + domainType + "\""); + } + return info.type; + } + + public String prepareClassName(String className) + { + return camelCaseName(className, true); + } + + public String prepareDomainName(String domainName) + { + return camelCaseName(domainName, false); + } + + public String prepareMethodName(String methodName) + { + return camelCaseName(methodName, false); + } + + private String camelCaseName(String name, boolean upperFirstFlag) + { + StringBuffer ccn = new StringBuffer(); + String[] toks = name.split("[-_.\\ ]"); + for (int i = 0; i < toks.length; i++) + { + StringBuffer b = new StringBuffer(toks[i]); + if (upperFirstFlag || i > 0) + { + b.setCharAt(0, Character.toUpperCase(toks[i].charAt(0))); + } + ccn.append(b); + } + return ccn.toString(); + } + + + public static Factory<DotnetGenerator> _factoryInstance = new Factory<DotnetGenerator>() + { + + public DotnetGenerator newInstance() + { + return new DotnetGenerator(); + } + }; + + public static Factory<DotnetGenerator> getFactory() + { + return _factoryInstance; + } + +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/GenerateMethod.java b/qpid/gentools/src/org/apache/qpid/gentools/GenerateMethod.java new file mode 100644 index 0000000000..8b0bb99b41 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/GenerateMethod.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.gentools; + + +public interface GenerateMethod +{ + String generate(String domainType, String fieldName, int ordinal, int indentSize, int tabSize); +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/Generator.java b/qpid/gentools/src/org/apache/qpid/gentools/Generator.java new file mode 100644 index 0000000000..5d6e7be527 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/Generator.java @@ -0,0 +1,857 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.gentools; + +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; +import org.w3c.dom.Node; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.LineNumberReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; + +public abstract class Generator implements LanguageConverter +{ + protected static String CR = Utils.LINE_SEPARATOR; + + + private static final Map<String, Integer> FIXED_SIZE_TYPES = new HashMap<String, Integer>(); + + static + { + FIXED_SIZE_TYPES.put("bit", 1); + FIXED_SIZE_TYPES.put("bitfield", 1); + FIXED_SIZE_TYPES.put("long", 4); + FIXED_SIZE_TYPES.put("longlong", 8); + FIXED_SIZE_TYPES.put("octet", 1); + FIXED_SIZE_TYPES.put("short", 2); + FIXED_SIZE_TYPES.put("timestamp", 8); + + } + + private String _templateDirectory; + private String _outputDirectory; + + public AmqpDomainMap getDomainMap() + { + return _domainMap; + } + + public AmqpConstantSet getConstantSet() + { + return _constantSet; + } + + public AmqpModel getModel() + { + return _model; + } + + abstract public String getNativeType(String type); + + abstract public String getEncodingType(String type); + + + + protected static enum EnumConstOutputTypes + { + OUTPUT_STRING, + OUTPUT_INTEGER, + OUTPUT_DOUBLE; + } + + ; + + public static enum TemplateType + { + model("model"), + clazz("class"), + method("method"), + field("field"); + + private final String _name; + + private TemplateType(String name) + { + _name = name; + } + + public String getName() + { + return _name; + } + } + + ; + + + public static interface Factory<X extends Generator> + { + public X newInstance(); + } + + + protected static final class NamedTemplate + { + private final String _name; + private final String _template; + private final File _file; + + + public NamedTemplate(String relativePath, File templateFile) + { + _file = templateFile; + _name = relativePath + Utils.FILE_SEPARATOR + templateFile.getName(); + + _template = loadTemplate(templateFile); + } + + + public String getName() + { + return _name; + } + + public String getTemplate() + { + return _template; + } + + + public File getFile() + { + return _file; + } + + } + + + private static final String VELOCITY_TEMPLATE_SUFFIX = ".vm"; + private static final String STANDARD_TEMPLATE_SUFFIX = ".tmpl"; + private static FilenameFilter _tmplFileFilter = new FilenameFilter() + { + + public boolean accept(File dir, String name) + { + return name.endsWith(STANDARD_TEMPLATE_SUFFIX) || name.endsWith(VELOCITY_TEMPLATE_SUFFIX); + } + }; + + + // This string is reproduced in every generated file as a comment + // TODO: Tie the version info into the build system. + protected static final String GENERATOR_INFO = "Qpid Gentools v.0.1"; + + + private final Map<TemplateType, Collection<NamedTemplate>> _templates = + new EnumMap<TemplateType, Collection<NamedTemplate>>(TemplateType.class); + + private final Map<TemplateType, Collection<NamedTemplate>> _versionSpecificTemplates = + new EnumMap<TemplateType, Collection<NamedTemplate>>(TemplateType.class); + + + private final AmqpVersionSet _versionSet; + + private final AmqpDomainMap _domainMap; + private final Map<AmqpVersion, AmqpDomainMap> _versionToDomainMapMap = new HashMap<AmqpVersion, AmqpDomainMap>(); + + private final AmqpConstantSet _constantSet; + private final Map<AmqpVersion, AmqpConstantSet> _versionToConstantSetMap = new HashMap<AmqpVersion, AmqpConstantSet>(); + + + public AmqpVersionSet getVersionSet() + { + return _versionSet; + } + + private final AmqpModel _model; + private final Map<AmqpVersion, AmqpModel> _versionToModelMap = new HashMap<AmqpVersion, AmqpModel>(); + + protected int generatedFileCounter; + + public Generator() + { + _versionSet = new AmqpVersionSet(); + _model = new AmqpModel(this); + _constantSet = new AmqpConstantSet(this); + _domainMap = new AmqpDomainMap(this); + + generatedFileCounter = 0; + } + +// public final AmqpVersionSet getVersionSet() +// { +// return _versionSet; +// } + + + public void addVersion(AmqpVersion version) + { + _versionSet.add(version); + if (!_versionToModelMap.containsKey(version)) + { + _versionToModelMap.put(version, new AmqpModel(this)); + } + if (!_versionToDomainMapMap.containsKey(version)) + { + _versionToDomainMapMap.put(version, new AmqpDomainMap(this)); + } + if (!_versionToConstantSetMap.containsKey(version)) + { + _versionToConstantSetMap.put(version, new AmqpConstantSet(this)); + } + } + + public int getNumberGeneratedFiles() + { + return generatedFileCounter; + } + +// public AmqpDomainMap getDomainMap() +// { +// return _domainMap; +// } +// +// public AmqpConstantSet getConstantSet() +// { +// return _constantSet; +// } +// +// +// public AmqpModel getModel() +// { +// return _model; +// } + + public void initializeTemplates() throws IOException + { + + for (TemplateType type : EnumSet.allOf(TemplateType.class)) + { + ArrayList<NamedTemplate> typeTemplates = new ArrayList<NamedTemplate>(); + _templates.put(type, typeTemplates); + ArrayList<NamedTemplate> versionSpecificTypeTemplates = new ArrayList<NamedTemplate>(); + _versionSpecificTemplates.put(type, versionSpecificTypeTemplates); + + File templateDirectory = new File(getTemplateDirectory() + Utils.FILE_SEPARATOR + type.getName()); + File versionTemplateDirectory = new File(getTemplateDirectory() + Utils.FILE_SEPARATOR + type.getName() + Utils.FILE_SEPARATOR + "version"); + + System.out.println("Looking for template files in directory: " + templateDirectory.getAbsoluteFile()); + + File[] templateFiles = templateDirectory.listFiles(_tmplFileFilter); + + File[] versionTemplateFiles = new File[0]; + + System.out.println("Looking for version specific template files in directory: " + versionTemplateDirectory.getAbsoluteFile()); + + if (versionTemplateDirectory.exists()) + { + versionTemplateFiles = versionTemplateDirectory.listFiles(_tmplFileFilter); + } + + if(templateFiles != null) + { + for (File templateFile : templateFiles) + { + System.out.println(type.getName() + " template file(s):"); + System.out.println(" " + templateFile.getCanonicalPath()); + typeTemplates.add(new NamedTemplate(type.getName(), templateFile)); + } + } + + if(versionTemplateFiles != null) + { + for (File versionTemplateFile : versionTemplateFiles) + { + System.out.println(type.getName() + " template file(s):"); + System.out.println(" " + versionTemplateFile.getCanonicalPath()); + versionSpecificTypeTemplates.add(new NamedTemplate(type.getName() + Utils.FILE_SEPARATOR + "version", versionTemplateFile)); + } + } + + } + } + + public String getTemplateDirectory() + { + return _templateDirectory; + } + + + public void setTemplateDirectory(String templateDirectory) + { + _templateDirectory = templateDirectory; + } + + + public void setOutputDirectory(String outputDirectory) + { + _outputDirectory = outputDirectory; + } + + public void generate() + { + prepareTargetDirectory(new File(_outputDirectory), true); + System.out.println("Generation directory: " + _outputDirectory); + + + processModelTemplates(_templates); + + for (AmqpClass amqpClass : _model.getClassMap().values()) + { + processClassTemplates(_templates, amqpClass); + + for (AmqpMethod amqpMethod : amqpClass.getMethodMap().values()) + { + processMethodTemplates(_templates, amqpClass, amqpMethod); + + for (AmqpField amqpField : amqpMethod.getFieldMap().values()) + { + processFieldTemplates(_templates, amqpClass, amqpMethod, amqpField, null); + } + } + } + + + for (AmqpVersion version : _versionSet) + { + AmqpModel model = _versionToModelMap.get(version); + processModelTemplates(_versionSpecificTemplates, version); + + for (AmqpClass amqpClass : model.getClassMap().values()) + { + processClassTemplates(_versionSpecificTemplates, amqpClass, version); + + for (AmqpMethod amqpMethod : amqpClass.getMethodMap().values()) + { + processMethodTemplates(_versionSpecificTemplates, amqpClass, amqpMethod, version); + + for (AmqpField amqpField : amqpMethod.getFieldMap().values()) + { + processFieldTemplates(_versionSpecificTemplates, amqpClass, amqpMethod, amqpField, version); + } + } + } + + } + } + + private void processMethodTemplates(Map<TemplateType, Collection<NamedTemplate>> templates, AmqpClass amqpClass, AmqpMethod amqpMethod, AmqpVersion version) + { + for (NamedTemplate template : templates.get(TemplateType.method)) + { + if(isVelocityTemplate(template)) + { + processVelocityTemplate(template,version,amqpClass,amqpMethod,null); + } + else + { + processMethodTemplate(template, amqpClass, amqpMethod); + } + } + + } + + private void processClassTemplates(Map<TemplateType, Collection<NamedTemplate>> templates, AmqpClass amqpClass, AmqpVersion version) + { + for (NamedTemplate template : templates.get(TemplateType.clazz)) + { + if(isVelocityTemplate(template)) + { + processVelocityTemplate(template,version,amqpClass,null,null); + } + else + { + processClassTemplate(template, amqpClass); + } + } + + } + + + private void processModelTemplates(Map<TemplateType, Collection<NamedTemplate>> templates, AmqpVersion version) + { + for (NamedTemplate template : templates.get(TemplateType.model)) + { + if (isVelocityTemplate(template)) + { + processModelVelocityTemplate(template, version); + } + else + { + processModelTemplate(template, version); + } + } + } + + abstract void processModelTemplate(NamedTemplate template, AmqpVersion version); + + + protected void processModelTemplates(Map<TemplateType, Collection<NamedTemplate>> templates) + { + for (NamedTemplate template : templates.get(TemplateType.model)) + { + if (isVelocityTemplate(template)) + { + processModelVelocityTemplate(template, null); + } + else + { + processModelTemplate(template); + } + } + } + + private boolean isVelocityTemplate(NamedTemplate template) + { + return template.getName().endsWith(VELOCITY_TEMPLATE_SUFFIX); + } + + private void processModelVelocityTemplate(NamedTemplate template, AmqpVersion version) + { + processVelocityTemplate(template,version,null,null,null); + } + + private void processVelocityTemplate(NamedTemplate template, AmqpVersion version, + AmqpClass amqpClass, AmqpMethod amqpMethod, AmqpField amqpField) + { + + VelocityContext context = new VelocityContext(); + + AmqpModel model = _model; + if(version != null) + { + model = _versionToModelMap.get(version); + } + context.put("model", model); + context.put("generator", GENERATOR_INFO); + + if (version != null) + { + context.put("version", version); + } + if(amqpClass != null) + { + context.put("amqpClass", amqpClass); + } + + if(amqpClass != null) + { + context.put("amqpMethod", amqpMethod); + } + + + StringWriter sw = new StringWriter(); + + + try + { + Template velocityTemplate = Velocity.getTemplate(template.getName()); + velocityTemplate.merge(context, sw); + String filename = String.valueOf(context.get("filename")); + + File outputFile = new File(getOutputDirectory() + Utils.FILE_SEPARATOR + filename); + outputFile.getParentFile().mkdirs(); + FileWriter outputFileWriter = new FileWriter(outputFile); + + outputFileWriter.append(sw.toString()); + outputFileWriter.close(); + + } + catch (Exception e) + { + e.printStackTrace(); + } + + + } + + + protected void processClassTemplates(Map<TemplateType, Collection<NamedTemplate>> templates, AmqpClass amqpClass) + { + for (NamedTemplate template : templates.get(TemplateType.clazz)) + { + if(isVelocityTemplate(template)) + { + processVelocityTemplate(template,null,amqpClass,null,null); + } + else + { + processClassTemplate(template, amqpClass); + } + } + } + + protected void processMethodTemplates(Map<TemplateType, Collection<NamedTemplate>> templates, AmqpClass amqpClass, AmqpMethod amqpMethod) + { + for (NamedTemplate template : templates.get(TemplateType.method)) + { + if(isVelocityTemplate(template)) + { + processVelocityTemplate(template,null,amqpClass,amqpMethod,null); + } + else + { + processMethodTemplate(template, amqpClass, amqpMethod); + } + } + } + + + protected void processFieldTemplates(Map<TemplateType, Collection<NamedTemplate>> templates, AmqpClass amqpClass, AmqpMethod amqpMethod, AmqpField amqpField, AmqpVersion amqpVersion) + { + for (NamedTemplate template : templates.get(TemplateType.field)) + { + if(isVelocityTemplate(template)) + { + processVelocityTemplate(template,amqpVersion,amqpClass,amqpMethod,amqpField); + } + else + { + processTemplate(template, amqpClass, amqpMethod, amqpField, amqpVersion); + } + } + } + + + protected void processVersionList(StringBuffer sb, int tokStart, int tokEnd) + { + int lend = sb.indexOf(Utils.LINE_SEPARATOR, tokStart) + 1; // Include cr at end of line + String tline = sb.substring(tokEnd, lend); // Line excluding line marker, including cr + sb.delete(tokStart, lend); + + for (AmqpVersion v : _versionSet) + { + // Insert copy of target line + StringBuffer isb = new StringBuffer(tline); + if (isb.indexOf("${protocol-version-list-entry}") >= 0) + { + String versionListEntry = " { ${major}, ${minor} }" + + (v.equals(_versionSet.last()) ? "" : ","); + replaceToken(isb, "${protocol-version-list-entry}", String.valueOf(versionListEntry)); + } + if (isb.indexOf("${major}") >= 0) + { + replaceToken(isb, "${major}", String.valueOf(v.getMajor())); + } + if (isb.indexOf("${minor}") >= 0) + { + replaceToken(isb, "${minor}", String.valueOf(v.getMinor())); + } + sb.insert(tokStart, isb.toString()); + tokStart += isb.length(); + } + } + + // Helper functions common to all generators + + protected static void prepareTargetDirectory(File dir, boolean createFlag) + { + if (dir.exists()) + { + if (!dir.isDirectory()) + { + throw new TargetDirectoryException("\"" + dir.getAbsolutePath() + + "\" exists, but is not a directory."); + } + } + else if (createFlag) // Create dir + { + if (!dir.mkdirs()) + { + throw new TargetDirectoryException("Unable to create directory \"" + + dir.getAbsolutePath() + "\"."); + } + } + else + { + throw new TargetDirectoryException("Directory \"" + dir.getAbsolutePath() + + "\" not found."); + } + + } + + protected void processAllLists(StringBuffer sb, AmqpClass thisClass, AmqpMethod method, AmqpVersion version) + { + AmqpModel model = (version == null) ? _model : _versionToModelMap.get(version); + + + int lstart = sb.indexOf("%{"); + while (lstart != -1) + { + int lend = sb.indexOf("}", lstart + 2); + if (lend > 0) + { + String listToken = sb.substring(lstart + 2, lend); + if (listToken.compareTo("VLIST") == 0) + { + processVersionList(sb, lstart, lend + 1); + } + else if (listToken.compareTo("CLIST") == 0) + { + processClassList(sb, lstart, lend + 1, model, version); + } + else if (listToken.compareTo("MLIST") == 0) + { + processMethodList(sb, lstart, lend + 1, thisClass); + } + else if (listToken.compareTo("FLIST") == 0) + { + // Pass the FieldMap from either a class or a method. + // If this is called from a class-level template, we assume that the + // class field list is required. In this case, method will be null. + processFieldList(sb, lstart, lend + 1, + (method == null ? thisClass.getFieldMap() : method.getFieldMap()), + version); + } + else if (listToken.compareTo("TLIST") == 0) + { + processConstantList(sb, lstart, lend + 1, _constantSet); + } + else + { + throw new AmqpTemplateException("Unknown list token \"%{" + listToken + + "}\" found in template at index " + lstart + "."); + } + } + lstart = sb.indexOf("%{", lstart + 1); + } + } + + protected void processAllTokens(StringBuffer sb, AmqpClass thisClass, AmqpMethod method, AmqpField field, + AmqpVersion version) + { + int lstart = sb.indexOf("${"); + while (lstart != -1) + { + int lend = sb.indexOf("}", lstart + 2); + if (lend > 0) + { + String token = sb.substring(lstart, lend + 1); + replaceToken(sb, lstart, token, processToken(token, thisClass, method, field, version)); + } + lstart = sb.indexOf("${", lstart); + } + } + + protected static void writeTargetFile(StringBuffer sb, File f) + { + try + { + f.getParentFile().mkdirs(); + FileWriter fw = new FileWriter(f); + fw.write(sb.toString().toCharArray()); + fw.flush(); + fw.close(); + } + catch (IOException e) + { + throw new AmqpTemplateException(e.getMessage()); + } + } + + + protected static String getTemplateFileName(StringBuffer sb) + { + if (sb.charAt(0) != '&') + { + throw new AmqpTemplateException("No filename marker &{filename} found at start of template."); + } + int cr = sb.indexOf(Utils.LINE_SEPARATOR); + if (cr < 0) + { + throw new AmqpTemplateException("Bad template structure - unable to find first line."); + } + String fileName = sb.substring(2, cr - 1); + sb.delete(0, cr + 1); + return fileName; + } + + protected static void replaceToken(StringBuffer sb, String token, String replacement) + { + replaceToken(sb, 0, token, replacement); + } + + protected static void replaceToken(StringBuffer sb, int index, String token, String replacement) + { + if (replacement != null) + { + int start = sb.indexOf(token, index); + if (start != -1) + { + int len = token.length(); + // Find first letter in token and determine if it is capitalized + char firstTokenLetter = getFirstLetter(token); + if (firstTokenLetter != 0 && Character.isUpperCase(firstTokenLetter)) + { + sb.replace(start, start + len, Utils.firstUpper(replacement)); + } + else + { + sb.replace(start, start + len, replacement); + } + } + } + } + + private static char getFirstLetter(String str) + { + int len = str.length(); + int index = 0; + char tokChar = str.charAt(index); + while (!Character.isLetter(tokChar) && index < len - 1) + { + tokChar = str.charAt(++index); + } + if (Character.isLetter(tokChar)) + { + return tokChar; + } + return 0; + } + + private static String loadTemplate(File f) + { + try + { + StringBuffer sb = new StringBuffer(); + FileReader fr = new FileReader(f); + LineNumberReader lnr = new LineNumberReader(fr); + String line = lnr.readLine(); + while (line != null) + { + + sb.append(line); + sb.append(Utils.LINE_SEPARATOR); + + line = lnr.readLine(); + } + lnr.close(); + fr.close(); + return sb.toString(); + } + catch (FileNotFoundException e) + { + throw new AmqpTemplateException("File not found: " + e.getMessage()); + } + catch (IOException e) + { + throw new AmqpTemplateException("IOException: " + e.getMessage()); + } + } + + public String getDomainType(String domainName, AmqpVersion version) + { + if (version == null) + { + version = _versionSet.first(); + } + return getDomainMap().getDomainType(domainName, version); + } + + + public void addFromNode(Node amqpNode, AmqpVersion version) + { + // 1c. Extract domains + getConstantSet().addFromNode(amqpNode, 0, version); + _versionToConstantSetMap.get(version).addFromNode(amqpNode, 0, version); + + // 1d. Extract domains + getDomainMap().addFromNode(amqpNode, 0, version); + _versionToDomainMapMap.get(version).addFromNode(amqpNode, 0, version); + + // 1e. Extract class/method/field heirarchy + getModel().addFromNode(amqpNode, 0, version); + _versionToModelMap.get(version).addFromNode(amqpNode, 0, version); + } + + + public String getOutputDirectory() + { + return _outputDirectory; + } + + public String prepareConstantName(String constantName) + { + return prepareDomainName(constantName); + } + + + public boolean isFixedSizeType(String type) + { + return FIXED_SIZE_TYPES.containsKey(type); + } + + + public int getTypeSize(String type) + { + return FIXED_SIZE_TYPES.get(type); + } + + + + // Model-level template processing + abstract protected void processModelTemplate(NamedTemplate template); + + // Class-level template processing + abstract protected void processClassTemplate(NamedTemplate template, AmqpClass thisClass); + + // Method-level template processing + abstract protected void processMethodTemplate(NamedTemplate template, AmqpClass thisClass, + AmqpMethod method); + + // Field-level template processing + abstract protected void processTemplate(NamedTemplate template, AmqpClass thisClass, + AmqpMethod method, AmqpField field, AmqpVersion version); + + abstract protected String prepareFilename(String filenameTemplate, AmqpClass thisClass, AmqpMethod method, + AmqpField field, AmqpVersion version); + + abstract protected String processToken(String token, AmqpClass thisClass, AmqpMethod method, + AmqpField field, AmqpVersion version); + + abstract protected void processClassList(StringBuffer sb, int listMarkerStartIndex, int listMarkerEndIndex, + AmqpModel model, AmqpVersion version); + + abstract protected void processMethodList(StringBuffer sb, int listMarkerStartIndex, int listMarkerEndIndex, + AmqpClass thisClass); + + + abstract protected void processFieldList(StringBuffer sb, int listMarkerStartIndex, int listMarkerEndIndex, + AmqpFieldMap fieldMap, AmqpVersion version); + + abstract protected void processConstantList(StringBuffer sb, int listMarkerStartIndex, int listMarkerEndIndex, + AmqpConstantSet constantSet); + + +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/JavaGenerator.java b/qpid/gentools/src/org/apache/qpid/gentools/JavaGenerator.java new file mode 100644 index 0000000000..7730fca1bd --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/JavaGenerator.java @@ -0,0 +1,1826 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.gentools; + +import java.io.File; +import java.util.Iterator; +import java.util.List; +import java.util.TreeMap; + +public class JavaGenerator extends Generator +{ + // TODO: Move this to parent class + protected static final int FIELD_NAME = 0; + protected static final int FIELD_CODE_TYPE = 1; + + private class DomainInfo + { + final public String type; + final public String size; + final public String encodingType; + final public String encodeExpression; + final public String decodeExpression; + + public DomainInfo(String domain, String size, String encodingType, String encodeExpression, String decodeExpression) + { + this.type = domain; + this.size = size; + this.encodeExpression = encodeExpression; + this.decodeExpression = decodeExpression; + this.encodingType = encodingType; + } + } + + private static TreeMap<String, DomainInfo> typeMap = new TreeMap<String, DomainInfo>(); + + // Methods used for generation of code snippets called from the field map parsers + + // Common methods + private final CommandGenerateMethod declarationGenerateMethod = new CommandGenerateMethod() + { + public String generate(String codeType, AmqpField field, AmqpVersionSet versionSet, int indentSize, int tabSize, boolean notLast) + { + return generateFieldDeclaration(codeType, field, versionSet, indentSize, tabSize, notLast); + } + }; + + private MangledGenerateMethod mangledDeclarationGenerateMethod = new MangledGenerateMethod() + { + public String generate(AmqpField field, int indentSize, int tabSize, boolean notLast) + { + return generateMangledFieldDeclaration(field, indentSize, tabSize, notLast); + } + }; + + // Methods for MessageBody classes + private CommandGenerateMethod mbGetGenerateMethod = new CommandGenerateMethod() + { + public String generate(String codeType, AmqpField field, AmqpVersionSet versionSet, int indentSize, int tabSize, boolean notLast) + { + return generateMbGetMethod(codeType, field, versionSet, indentSize, tabSize, notLast); //To change body of implemented methods use File | Settings | File Templates. + } + }; + + private MangledGenerateMethod mbMangledGetGenerateMethod = new MangledGenerateMethod() + { + public String generate(AmqpField field, int indentSize, int tabSize, boolean notLast) + { + return generateMbMangledGetMethod(field, indentSize, tabSize, notLast); + } + }; + private CommandGenerateMethod mbParamListGenerateMethod = new CommandGenerateMethod() + { + public String generate(String codeType, AmqpField field, AmqpVersionSet versionSet, int indentSize, int tabSize, boolean notLast) + { + return generateMbParamList(codeType, field, versionSet, indentSize, tabSize, notLast); + } + }; + private CommandGenerateMethod mbPassedParamListGenerateMethod = new CommandGenerateMethod() + { + public String generate(String codeType, AmqpField field, AmqpVersionSet versionSet, int indentSize, int tabSize, boolean notLast) + { + return generateMbPassedParamList(codeType, field, versionSet, indentSize, tabSize, notLast); + } + }; + private MangledGenerateMethod mbMangledParamListGenerateMethod = new MangledGenerateMethod() + { + public String generate(AmqpField field, int indentSize, int tabSize, boolean notLast) + { + return generateMbMangledParamList(field, indentSize, tabSize, notLast); + } + }; + private MangledGenerateMethod mbMangledPassedParamListGenerateMethod = new MangledGenerateMethod() + { + public String generate(AmqpField field, int indentSize, int tabSize, boolean notLast) + { + return generateMbMangledPassedParamList(field, indentSize, tabSize, notLast); + } + }; + private CommandGenerateMethod mbBodyInitGenerateMethod = new CommandGenerateMethod() + { + public String generate(String codeType, AmqpField field, AmqpVersionSet versionSet, int indentSize, int tabSize, boolean notLast) + { + return generateMbBodyInit(codeType, field, versionSet, indentSize, tabSize, notLast); + } + }; + private MangledGenerateMethod mbMangledBodyInitGenerateMethod = new MangledGenerateMethod() + { + public String generate(AmqpField field, int indentSize, int tabSize, boolean notLast) + { + return generateMbMangledBodyInit(field, indentSize, tabSize, notLast); + } + }; + private GenerateMethod mbSizeGenerateMethod = new GenerateMethod() + { + public String generate(String domainType, String fieldName, int ordinal, int indentSize, int tabSize) + { + return generateMbFieldSize(domainType, fieldName, ordinal, indentSize, tabSize); + } + }; + private BitFieldGenerateMethod mbBitSizeGenerateMethod = new BitFieldGenerateMethod() + { + public String generate(List<String> bitFieldList, int ordinal, int indentSize, int tabSize) + { + return generateMbBitArrayFieldSize(bitFieldList, ordinal, indentSize, tabSize); + } + }; + private GenerateMethod mbEncodeGenerateMethod = new GenerateMethod() + { + public String generate(String domainType, String fieldName, int ordinal, int indentSize, int tabSize) + { + return generateMbFieldEncode(domainType, fieldName, ordinal, indentSize, tabSize); + } + }; + private BitFieldGenerateMethod mbBitEncodeGenerateMethod = new BitFieldGenerateMethod() + { + public String generate(List<String> bitFieldList, int ordinal, int indentSize, int tabSize) + { + return generateMbBitFieldEncode(bitFieldList, ordinal, indentSize, tabSize); + } + }; + private GenerateMethod mbDecodeGenerateMethod = new GenerateMethod() + { + public String generate(String domainType, String fieldName, int ordinal, int indentSize, int tabSize) + { + return generateMbFieldDecode(domainType, fieldName, ordinal, indentSize, tabSize); + } + }; + private BitFieldGenerateMethod mbBitDecodeGenerateMethod = new BitFieldGenerateMethod() + { + public String generate(List<String> bitFieldList, int ordinal, int indentSize, int tabSize) + { + return generateMbBitFieldDecode(bitFieldList, ordinal, indentSize, tabSize); + } + }; + private GenerateMethod mbToStringGenerateMethod = new GenerateMethod() + { + public String generate(String domainType, String fieldName, int ordinal, int indentSize, int tabSize) + { + return generateMbFieldToString(domainType, fieldName, ordinal, indentSize, tabSize); + } + }; + private BitFieldGenerateMethod mbBitToStringGenerateMethod = new BitFieldGenerateMethod() + { + public String generate(List<String> bitFieldList, int ordinal, int indentSize, int tabSize) + { + return generateMbBitFieldToString(bitFieldList, ordinal, indentSize, tabSize); + } + }; + + // Methods for PropertyContentHeader classes + private CommandGenerateMethod pchClearGenerateMethod = new CommandGenerateMethod() + { + public String generate(String codeType, AmqpField field, AmqpVersionSet versionSet, int indentSize, int tabSize, boolean notLast) + { + return generatePchClearMethod(codeType, field, versionSet, indentSize, tabSize, notLast); + } + }; + private MangledGenerateMethod pchMangledClearGenerateMethod = new MangledGenerateMethod() + { + public String generate(AmqpField field, int indentSize, int tabSize, boolean notLast) + { + return generatePchMangledClearMethod(field, indentSize, tabSize, notLast); + } + }; + private CommandGenerateMethod pchGetGenerateMethod = new CommandGenerateMethod() + { + public String generate(String codeType, AmqpField field, AmqpVersionSet versionSet, int indentSize, int tabSize, boolean notLast) + { + return generatePchGetMethod(codeType, field, versionSet, indentSize, tabSize, notLast); + } + }; + private MangledGenerateMethod pchMangledGetGenerateMethod = new MangledGenerateMethod() + { + public String generate(AmqpField field, int indentSize, int tabSize, boolean notLast) + { + return generatePchMangledGetMethod(field, indentSize, tabSize, notLast); + } + }; + private CommandGenerateMethod pchSetGenerateMethod = new CommandGenerateMethod() + { + public String generate(String codeType, AmqpField field, AmqpVersionSet versionSet, int indentSize, int tabSize, boolean notLast) + { + return generatePchSetMethod(codeType, field, versionSet, indentSize, tabSize, notLast); + } + }; + private MangledGenerateMethod pchMangledSetGenerateMethod = new MangledGenerateMethod() + { + public String generate(AmqpField field, int indentSize, int tabSize, boolean notLast) + { + return generatePchMangledSetMethod(field, indentSize, tabSize, notLast); + } + }; + private GenerateMethod pchSizeGenerateMethod = new GenerateMethod() + { + public String generate(String domainType, String fieldName, int ordinal, int indentSize, int tabSize) + { + return generatePchFieldSize(domainType, fieldName, ordinal, indentSize, tabSize); + } + }; + private BitFieldGenerateMethod pchBitSizeGenerateMethod = new BitFieldGenerateMethod() + { + public String generate(List<String> bitFieldList, int ordinal, int indentSize, int tabSize) + { + return generatePchBitArrayFieldSize(bitFieldList, ordinal, indentSize, tabSize); + } + }; + private GenerateMethod pchEncodeGenerateMethod = new GenerateMethod() + { + public String generate(String domainType, String fieldName, int ordinal, int indentSize, int tabSize) + { + return generatePchFieldEncode(domainType, fieldName, ordinal, indentSize, tabSize); + } + }; + private BitFieldGenerateMethod pchBitEncodeGenerateMethod = new BitFieldGenerateMethod() + { + public String generate(List<String> bitFieldList, int ordinal, int indentSize, int tabSize) + { + return generatePchBitFieldEncode(bitFieldList, ordinal, indentSize, tabSize); + } + }; + private GenerateMethod pchDecodeGenerateMethod = new GenerateMethod() + { + public String generate(String domainType, String fieldName, int ordinal, int indentSize, int tabSize) + { + return generatePchFieldDecode(domainType, fieldName, ordinal, indentSize, tabSize); + } + }; + private BitFieldGenerateMethod pchBitDecodeGenerateMethod = new BitFieldGenerateMethod() + { + public String generate(List<String> bitFieldList, int ordinal, int indentSize, int tabSize) + { + return generatePchBitFieldDecode(bitFieldList, ordinal, indentSize, tabSize); + } + }; + private GenerateMethod pchGetPropertyFlagsGenerateMethod = new GenerateMethod() + { + public String generate(String domainType, String fieldName, int ordinal, int indentSize, int tabSize) + { + return generatePchGetPropertyFlags(domainType, fieldName, ordinal, indentSize, tabSize); + } + }; + private BitFieldGenerateMethod pchBitGetPropertyFlagsGenerateMethod = new BitFieldGenerateMethod() + { + public String generate(List<String> bitFieldList, int ordinal, int indentSize, int tabSize) + { + return generatePchBitGetPropertyFlags(bitFieldList, ordinal, indentSize, tabSize); + } + }; + private GenerateMethod pchSetPropertyFlagsGenerateMethod = new GenerateMethod() + { + public String generate(String domainType, String fieldName, int ordinal, int indentSize, int tabSize) + { + return generatePchSetPropertyFlags(domainType, fieldName, ordinal, indentSize, tabSize); + } + }; + private BitFieldGenerateMethod pchBitSetPropertyFlagsGenerateMethod = new BitFieldGenerateMethod() + { + public String generate(List<String> bitFieldList, int ordinal, int indentSize, int tabSize) + { + return generatePchBitSetPropertyFlags(bitFieldList, ordinal, indentSize, tabSize); + } + }; + + + public String getNativeType(String type) + { + return typeMap.get(type).type; + } + + public String getEncodingType(String type) + { + return typeMap.get(type).encodingType; + } + + + public JavaGenerator() + { + super(); + // Load Java type and size maps. + // Adjust or add to these lists as new types are added/defined. + // The char '#' will be replaced by the field variable name (any type). + // The char '~' will be replaced by the compacted bit array size (type bit only). + typeMap.put("bit", new DomainInfo( + "boolean", // Java code type + "~", // size + "Boolean", // Java code type + "EncodingUtils.writeBooleans(buffer, #)", // encode expression + "# = EncodingUtils.readBooleans(buffer)")); // decode expression + typeMap.put("bitfield", new DomainInfo( + "byte", // Java code type + "~", // size + "Bitfield", + "EncodingUtils.writeBooleans(buffer, #)", // encode expression + "# = EncodingUtils.readBooleans(buffer)")); // decode expression + + typeMap.put("content", new DomainInfo( + "Content", // Java code type + "EncodingUtils.encodedContentLength(#)", // size + "Content", // Java code type + "EncodingUtils.writeContentBytes(buffer, #)", // encode expression + "# = EncodingUtils.readContent(buffer)")); // decode expression + typeMap.put("long", new DomainInfo( + "long", // Java code type + "4", // size + "UnsignedInteger", // Java code type + "EncodingUtils.writeUnsignedInteger(buffer, #)", // encode expression + "# = buffer.getUnsignedInt()")); // decode expression + typeMap.put("longlong", new DomainInfo( + "long", // Java code type + "8", // size + "Long", + "buffer.putLong(#)", // encode expression + "# = buffer.getLong()")); // decode expression + typeMap.put("longstr", new DomainInfo( + "byte[]", // Java code type + "EncodingUtils.encodedLongstrLength(#)", // size + "Bytes", + "EncodingUtils.writeLongStringBytes(buffer, #)", // encode expression + "# = EncodingUtils.readLongstr(buffer)")); // decode expression + typeMap.put("octet", new DomainInfo( + "short", // Java code type + "1", // size + "UnsignedByte", + "EncodingUtils.writeUnsignedByte(buffer, #)", // encode expression + "# = buffer.getUnsigned()")); // decode expression + typeMap.put("short", new DomainInfo( + "int", // Java code type + "2", // size + "UnsignedShort", + "EncodingUtils.writeUnsignedShort(buffer, #)", // encode expression + "# = buffer.getUnsignedShort()")); // decode expression + typeMap.put("shortstr", new DomainInfo( + "AMQShortString", // Java code type + "EncodingUtils.encodedShortStringLength(#)", // size + "AMQShortString", // Java code type + "EncodingUtils.writeShortStringBytes(buffer, #)", // encode expression + "# = EncodingUtils.readAMQShortString(buffer)")); // decode expression + typeMap.put("table", new DomainInfo( + "FieldTable", // Java code type + "EncodingUtils.encodedFieldTableLength(#)", // size + "FieldTable", // Java code type + "EncodingUtils.writeFieldTableBytes(buffer, #)", // encode expression + "# = EncodingUtils.readFieldTable(buffer)")); // decode expression + typeMap.put("timestamp", new DomainInfo( + "long", // Java code type + "8", // size + "Timestamp", + "EncodingUtils.writeTimestamp(buffer, #)", // encode expression + "# = EncodingUtils.readTimestamp(buffer)")); // decode expression + } + + // === Start of methods for Interface LanguageConverter === + + public String prepareClassName(String className) + { + return camelCaseName(className, true); + } + + public String prepareMethodName(String methodName) + { + return camelCaseName(methodName, false); + } + + public String prepareDomainName(String domainName) + { + return camelCaseName(domainName, false); + } + + + public String getGeneratedType(String domainName, AmqpVersion version) + { + String domainType = getDomainType(domainName, version); + if (domainType == null) + { + throw new AmqpTypeMappingException("Domain type \"" + domainName + + "\" not found in Java typemap."); + } + DomainInfo info = typeMap.get(domainType); + if (info == null) + { + throw new AmqpTypeMappingException("Unknown domain: \"" + domainType + "\""); + } + return info.type; + } + + // === Abstract methods from class Generator - Java-specific implementations === + + @Override + protected String prepareFilename(String filenameTemplate, AmqpClass thisClass, AmqpMethod method, + AmqpField field, AmqpVersion version) + { + StringBuffer sb = new StringBuffer(filenameTemplate); + if (thisClass != null) + { + replaceToken(sb, "${CLASS}", thisClass.getName()); + } + if (method != null) + { + replaceToken(sb, "${METHOD}", method.getName()); + } + if (field != null) + { + replaceToken(sb, "${FIELD}", field.getName()); + } + if (version != null) + { + replaceToken(sb, "${MAJOR}", String.valueOf(version.getMajor())); + replaceToken(sb, "${MINOR}", String.valueOf(version.getMinor())); + } + return sb.toString(); + } + + @Override + protected void processModelTemplate(NamedTemplate template) + { + processTemplate(template, null, null, null, null); + } + + @Override + protected void processClassTemplate(NamedTemplate template, AmqpClass thisClass) + { + processTemplate(template, thisClass, null, null, + thisClass.getVersionSet().size() == 1 ? thisClass.getVersionSet().first() : null); + } + + @Override + protected void processMethodTemplate(NamedTemplate template, AmqpClass thisClass, + AmqpMethod method) + { + processTemplate(template, thisClass, method, null, + thisClass.getVersionSet().size() == 1 ? thisClass.getVersionSet().first() : null); + } + + protected void processFieldTemplate(NamedTemplate template, AmqpClass thisClass, + AmqpMethod method, AmqpField field) + { + processTemplate(template, thisClass, method, field, + thisClass.getVersionSet().size() == 1 ? thisClass.getVersionSet().first() : null); + } + + @Override + protected void processTemplate(NamedTemplate template, AmqpClass thisClass, + AmqpMethod method, AmqpField field, AmqpVersion version) + { + StringBuffer sb = new StringBuffer(template.getTemplate()); + String filename = prepareFilename(getTemplateFileName(sb), thisClass, method, field, version); + processTemplate(sb, thisClass, method, field, template.getName(), version); + writeTargetFile(sb, new File(getOutputDirectory() + Utils.FILE_SEPARATOR + filename)); + generatedFileCounter++; + } + + protected void processTemplate(StringBuffer sb, AmqpClass thisClass, AmqpMethod method, + AmqpField field, String templateFileName, AmqpVersion version) + { + try + { + processAllLists(sb, thisClass, method, version); + } + catch (AmqpTemplateException e) + { + System.out.println("WARNING: " + templateFileName + ": " + e.getMessage()); + } + try + { + processAllTokens(sb, thisClass, method, field, version); + } + catch (AmqpTemplateException e) + { + System.out.println("WARNING: " + templateFileName + ": " + e.getMessage()); + } + } + + @Override + protected String processToken(String token, AmqpClass thisClass, AmqpMethod method, AmqpField field, + AmqpVersion version) + { + if (token.compareTo("${GENERATOR}") == 0) + { + return GENERATOR_INFO; + } + if (token.compareTo("${CLASS}") == 0 && thisClass != null) + { + return thisClass.getName(); + } + if (token.compareTo("${CLASS_ID_INIT}") == 0 && thisClass != null) + { + return generateIndexInitializer("registerClassId", thisClass.getIndexMap(), 8); + } + if (token.compareTo("${METHOD}") == 0 && method != null) + { + return method.getName(); + } + if (token.compareTo("${METHOD_ID_INIT}") == 0 && method != null) + { + return generateIndexInitializer("registerMethodId", method.getIndexMap(), 8); + } + if (token.compareTo("${FIELD}") == 0 && field != null) + { + return field.getName(); + } + + // This token is used only with class or method-level templates + if (token.compareTo("${pch_property_flags_declare}") == 0) + { + return generatePchPropertyFlagsDeclare(); + } + else if (token.compareTo("${pch_property_flags_initializer}") == 0) + { + int mapSize = method == null ? thisClass.getFieldMap().size() : method.getFieldMap().size(); + return generatePchPropertyFlagsInitializer(mapSize); + } + else if (token.compareTo("${pch_compact_property_flags_initializer}") == 0) + { + return generatePchCompactPropertyFlagsInitializer(thisClass, 8, 4); + } + else if (token.compareTo("${pch_compact_property_flags_check}") == 0) + { + return generatePchCompactPropertyFlagsCheck(thisClass, 8, 4); + } + + // Oops! + throw new AmqpTemplateException("Template token " + token + " unknown."); + } + + @Override + protected void processClassList(StringBuffer sb, int listMarkerStartIndex, int listMarkerEndIndex, + AmqpModel model, AmqpVersion version) + { + String codeSnippet; + int lend = sb.indexOf(CR, listMarkerStartIndex) + 1; // Include cr at end of line + String tline = sb.substring(listMarkerEndIndex, lend); // Line excluding line marker, including cr + int tokStart = tline.indexOf('$'); + String token = tline.substring(tokStart).trim(); + sb.delete(listMarkerStartIndex, lend); + + if (token.compareTo("${reg_map_put_method}") == 0) + { + codeSnippet = generateRegistry(model, 8, 4); + } + + else // Oops! + { + throw new AmqpTemplateException("Template token " + token + " unknown."); + } + + sb.insert(listMarkerStartIndex, codeSnippet); + } + + @Override + protected void processMethodList(StringBuffer sb, int listMarkerStartIndex, int listMarkerEndIndex, + AmqpClass thisClass) + { + String codeSnippet; + int lend = sb.indexOf(CR, listMarkerStartIndex) + 1; // Include cr at end of line + String tline = sb.substring(listMarkerEndIndex, lend); // Line excluding line marker, including cr + int tokStart = tline.indexOf('$'); + String token = tline.substring(tokStart).trim(); + sb.delete(listMarkerStartIndex, lend); + + //TODO - we don't have any cases of this (yet). + if (token.compareTo("${???}") == 0) + { + codeSnippet = token; + } + else // Oops! + { + throw new AmqpTemplateException("Template token " + token + " unknown."); + } + + sb.insert(listMarkerStartIndex, codeSnippet); + } + + @Override + protected void processFieldList(StringBuffer sb, int listMarkerStartIndex, int listMarkerEndIndex, + AmqpFieldMap fieldMap, AmqpVersion version) + { + String codeSnippet; + int lend = sb.indexOf(CR, listMarkerStartIndex) + 1; // Include cr at end of line + String tline = sb.substring(listMarkerEndIndex, lend); // Line excluding line marker, including cr + int tokStart = tline.indexOf('$'); + String token = tline.substring(tokStart).trim(); + sb.delete(listMarkerStartIndex, lend); + + // Field declarations - common to MethodBody and PropertyContentHeader classes + if (token.compareTo("${field_declaration}") == 0) + { + codeSnippet = fieldMap.parseFieldMap(declarationGenerateMethod, + mangledDeclarationGenerateMethod, 4, 4, this); + } + + // MethodBody classes + else if (token.compareTo("${mb_field_get_method}") == 0) + { + codeSnippet = fieldMap.parseFieldMap(mbGetGenerateMethod, + mbMangledGetGenerateMethod, 4, 4, this); + } + else if (token.compareTo("${mb_field_parameter_list}") == 0) + { + // <cringe> The code generated by this is ugly... It puts a comma on a line by itself! + // TODO: Find a more elegant solution here sometime... + codeSnippet = fieldMap.size() > 0 ? Utils.createSpaces(42) + "," + CR : ""; + // </cringe> + codeSnippet += fieldMap.parseFieldMap(mbParamListGenerateMethod, + mbMangledParamListGenerateMethod, 42, 4, this); + } + + else if (token.compareTo("${mb_field_passed_parameter_list}") == 0) + { + // <cringe> The code generated by this is ugly... It puts a comma on a line by itself! + // TODO: Find a more elegant solution here sometime... + codeSnippet = fieldMap.size() > 0 ? Utils.createSpaces(42) + "," + CR : ""; + // </cringe> + codeSnippet += fieldMap.parseFieldMap(mbPassedParamListGenerateMethod, + mbMangledPassedParamListGenerateMethod, 42, 4, this); + } + else if (token.compareTo("${mb_field_body_initialize}") == 0) + { + codeSnippet = fieldMap.parseFieldMap(mbBodyInitGenerateMethod, + mbMangledBodyInitGenerateMethod, 8, 4, this); + } + else if (token.compareTo("${mb_field_size}") == 0) + { + codeSnippet = fieldMap.parseFieldMapOrdinally(mbSizeGenerateMethod, + mbBitSizeGenerateMethod, 8, 4, this); + } + else if (token.compareTo("${mb_field_encode}") == 0) + { + codeSnippet = fieldMap.parseFieldMapOrdinally(mbEncodeGenerateMethod, + mbBitEncodeGenerateMethod, 8, 4, this); + } + else if (token.compareTo("${mb_field_decode}") == 0) + { + codeSnippet = fieldMap.parseFieldMapOrdinally(mbDecodeGenerateMethod, + mbBitDecodeGenerateMethod, 8, 4, this); + } + else if (token.compareTo("${mb_field_to_string}") == 0) + { + codeSnippet = fieldMap.parseFieldMapOrdinally(mbToStringGenerateMethod, + mbBitToStringGenerateMethod, 8, 4, this); + } + + // PropertyContentHeader classes + else if (token.compareTo("${pch_field_list_size}") == 0) + { + codeSnippet = fieldMap.parseFieldMapOrdinally(pchSizeGenerateMethod, + pchBitSizeGenerateMethod, 12, 4, this); + } + else if (token.compareTo("${pch_field_list_payload}") == 0) + { + codeSnippet = fieldMap.parseFieldMapOrdinally(pchEncodeGenerateMethod, + pchBitEncodeGenerateMethod, 12, 4, this); + } + else if (token.compareTo("${pch_field_list_decode}") == 0) + { + codeSnippet = fieldMap.parseFieldMapOrdinally(pchDecodeGenerateMethod, + pchBitDecodeGenerateMethod, 12, 4, this); + } + else if (token.compareTo("${pch_get_compact_property_flags}") == 0) + { + codeSnippet = fieldMap.parseFieldMapOrdinally(pchGetPropertyFlagsGenerateMethod, + pchBitGetPropertyFlagsGenerateMethod, 8, 4, this); + } + else if (token.compareTo("${pch_set_compact_property_flags}") == 0) + { + codeSnippet = fieldMap.parseFieldMapOrdinally(pchSetPropertyFlagsGenerateMethod, + pchBitSetPropertyFlagsGenerateMethod, 8, 4, this); + } + else if (token.compareTo("${pch_field_clear_methods}") == 0) + { + codeSnippet = fieldMap.parseFieldMap(pchClearGenerateMethod, + pchMangledClearGenerateMethod, 4, 4, this); + } + else if (token.compareTo("${pch_field_get_methods}") == 0) + { + codeSnippet = fieldMap.parseFieldMap(pchGetGenerateMethod, + pchMangledGetGenerateMethod, 4, 4, this); + } + else if (token.compareTo("${pch_field_set_methods}") == 0) + { + codeSnippet = fieldMap.parseFieldMap(pchSetGenerateMethod, + pchMangledSetGenerateMethod, 4, 4, this); + } + + else // Oops! + { + throw new AmqpTemplateException("Template token " + token + " unknown."); + } + sb.insert(listMarkerStartIndex, codeSnippet); + } + + @Override + protected void processConstantList(StringBuffer sb, int listMarkerStartIndex, int listMarkerEndIndex, + AmqpConstantSet constantSet) + { + String codeSnippet; + int lend = sb.indexOf(CR, listMarkerStartIndex) + 1; // Include cr at end of line + String tline = sb.substring(listMarkerEndIndex, lend); // Line excluding line marker, including cr + int tokStart = tline.indexOf('$'); + String token = tline.substring(tokStart).trim(); + sb.delete(listMarkerStartIndex, lend); + + if (token.compareTo("${const_get_method}") == 0) + { + codeSnippet = generateConstantGetMethods(constantSet, 4, 4); + } + + else // Oops! + { + throw new AmqpTemplateException("Template token " + token + " unknown."); + } + + sb.insert(listMarkerStartIndex, codeSnippet); + } + + // === Protected and private helper functions unique to Java implementation === + + // Methods used for generation of code snippets called from the field map parsers + + // Common methods + + protected String generateFieldDeclaration(String codeType, AmqpField field, + AmqpVersionSet versionSet, int indentSize, int tabSize, boolean nextFlag) + { + return Utils.createSpaces(indentSize) + "public " + codeType + " " + field.getName() + + "; // AMQP version(s): " + versionSet + CR; + } + + protected String generateMangledFieldDeclaration(AmqpField field, int indentSize, + int tabSize, boolean nextFlag) + { + StringBuffer sb = new StringBuffer(); + Iterator<String> dItr = field.getDomainMap().keySet().iterator(); + int domainCntr = 0; + while (dItr.hasNext()) + { + String domainName = dItr.next(); + AmqpVersionSet versionSet = field.getDomainMap().get(domainName); + String codeType = getGeneratedType(domainName, versionSet.first()); + sb.append(Utils.createSpaces(indentSize) + "public " + codeType + " " + + field.getName() + "_" + (domainCntr++) + "; // AMQP Version(s): " + versionSet + + CR); + } + return sb.toString(); + } + + protected String generateIndexInitializer(String mapName, AmqpOrdinalVersionMap indexMap, int indentSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + + Iterator<Integer> iItr = indexMap.keySet().iterator(); + while (iItr.hasNext()) + { + int index = iItr.next(); + AmqpVersionSet versionSet = indexMap.get(index); + Iterator<AmqpVersion> vItr = versionSet.iterator(); + while (vItr.hasNext()) + { + AmqpVersion version = vItr.next(); + sb.append(indent + mapName + "( (byte) " + version.getMajor() + ", (byte) " + version.getMinor() + ", " + index + ");" + CR); + } + } + return sb.toString(); + } + + protected String generateRegistry(AmqpModel model, int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + StringBuffer sb = new StringBuffer(); + + for (String className : model.getClassMap().keySet()) + { + AmqpClass thisClass = model.getClassMap().get(className); + for (String methodName : thisClass.getMethodMap().keySet()) + { + AmqpMethod method = thisClass.getMethodMap().get(methodName); + for (AmqpVersion version : model.getVersionSet()) + { + // Find class and method index for this version (if it exists) + try + { + int classIndex = findIndex(thisClass.getIndexMap(), version); + int methodIndex = findIndex(method.getIndexMap(), version); + sb.append(indent + "registerMethod(" + CR); + sb.append(indent + tab + "(short)" + classIndex + + ", (short)" + methodIndex + ", (byte)" + version.getMajor() + + ", (byte)" + version.getMinor() + ", " + CR); + sb.append(indent + tab + Utils.firstUpper(thisClass.getName()) + + Utils.firstUpper(method.getName()) + "Body.getFactory());" + CR); + } + catch (Exception e) + { + } // Ignore + } + } + } + return sb.toString(); + } + + protected int findIndex(TreeMap<Integer, AmqpVersionSet> map, AmqpVersion version) + { + Iterator<Integer> iItr = map.keySet().iterator(); + while (iItr.hasNext()) + { + int index = iItr.next(); + AmqpVersionSet versionSet = map.get(index); + if (versionSet.contains(version)) + { + return index; + } + } + throw new IllegalArgumentException("Index not found"); + } + + // Methods for AmqpConstants class + + + public String prepareConstantName(String constantName) + { + return upperCaseName(constantName); + } + + + protected String generateConstantGetMethods(AmqpConstantSet constantSet, + int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + + for (AmqpConstant constant : constantSet.getContstants()) + { + + if (constant.isVersionConsistent(constantSet.getVersionSet())) + { + // return a constant + String value = constant.firstKey(); + if (Utils.containsOnlyDigits(value)) + { + sb.append(indent + "public static final int " + constant.getName() + " = " + + constant.firstKey() + ";" + CR); + } + else if (Utils.containsOnlyDigitsAndDecimal(value)) + { + sb.append(indent + "public static double " + constant.getName() + " = " + + constant.firstKey() + "; " + CR); + } + else + { + sb.append(indent + "public static String " + constant.getName() + " = " + + constant.firstKey() + "\"; " + CR); + + } + sb.append(CR); + } + else + { + // Return version-specific constant + sb.append(generateVersionDependentGet(constant, "String", "", "\"", "\"", indentSize, tabSize)); + sb.append(generateVersionDependentGet(constant, "int", "AsInt", "", "", indentSize, tabSize)); + sb.append(generateVersionDependentGet(constant, "double", "AsDouble", "(double)", "", indentSize, tabSize)); + sb.append(CR); + } + } + return sb.toString(); + } + + protected String generateVersionDependentGet(AmqpConstant constant, + String methodReturnType, String methodNameSuffix, String returnPrefix, String returnPostfix, + int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + StringBuffer sb = new StringBuffer(); + sb.append(indent + "public static " + methodReturnType + " " + constant.getName() + + methodNameSuffix + "(byte major, byte minor) throws AMQProtocolVersionException" + CR); + sb.append(indent + "{" + CR); + boolean first = true; + Iterator<String> sItr = constant.keySet().iterator(); + while (sItr.hasNext()) + { + String value = sItr.next(); + AmqpVersionSet versionSet = constant.get(value); + sb.append(indent + tab + (first ? "" : "else ") + "if (" + generateVersionCheck(versionSet) + + ")" + CR); + sb.append(indent + tab + "{" + CR); + if (methodReturnType.compareTo("int") == 0 && !Utils.containsOnlyDigits(value)) + { + sb.append(generateConstantDeclarationException(constant.getName(), methodReturnType, + indentSize + (2 * tabSize), tabSize)); + } + else if (methodReturnType.compareTo("double") == 0 && !Utils.containsOnlyDigitsAndDecimal(value)) + { + sb.append(generateConstantDeclarationException(constant.getName(), methodReturnType, + indentSize + (2 * tabSize), tabSize)); + } + else + { + sb.append(indent + tab + tab + "return " + returnPrefix + value + returnPostfix + ";" + CR); + } + sb.append(indent + tab + "}" + CR); + first = false; + } + sb.append(indent + tab + "else" + CR); + sb.append(indent + tab + "{" + CR); + sb.append(indent + tab + tab + "throw new AMQProtocolVersionException(\"Constant \\\"" + + constant.getName() + "\\\" \" +" + CR); + sb.append(indent + tab + tab + tab + + "\"is undefined for AMQP version \" + major + \"-\" + minor + \".\");" + CR); + sb.append(indent + tab + "}" + CR); + sb.append(indent + "}" + CR); + return sb.toString(); + } + + protected String generateConstantDeclarationException(String name, String methodReturnType, + int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + StringBuffer sb = new StringBuffer(); + sb.append(indent + "throw new AMQProtocolVersionException(\"Constant \\\"" + + name + "\\\" \" +" + CR); + sb.append(indent + tab + "\"cannot be converted to type " + methodReturnType + + " for AMQP version \" + major + \"-\" + minor + \".\");" + CR); + return sb.toString(); + } + + // Methods for MessageBody classes + protected String generateMbGetMethod(String codeType, AmqpField field, + AmqpVersionSet versionSet, int indentSize, int tabSize, boolean nextFlag) + { + return Utils.createSpaces(indentSize) + "public " + codeType + " get" + + Utils.firstUpper(field.getName()) + "() { return " + field.getName() + "; }" + + CR; + } + + protected String generateMbMangledGetMethod(AmqpField field, int indentSize, + int tabSize, boolean nextFlag) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + StringBuffer sb = new StringBuffer(CR); + sb.append(indent + "public <T> T get" + Utils.firstUpper(field.getName()) + + "(Class<T> classObj) throws AMQProtocolVersionException" + CR); + sb.append(indent + "{" + CR); + Iterator<String> dItr = field.getDomainMap().keySet().iterator(); + int domainCntr = 0; + while (dItr.hasNext()) + { + String domainName = dItr.next(); + AmqpVersionSet versionSet = field.getDomainMap().get(domainName); + String codeType = getGeneratedType(domainName, versionSet.first()); + sb.append(indent + tab + "if (classObj.equals(" + codeType + + ".class)) // AMQP Version(s): " + versionSet + CR); + sb.append(indent + tab + tab + "return (T)(Object)" + field.getName() + "_" + + (domainCntr++) + ";" + CR); + } + sb.append(indent + tab + + "throw new AMQProtocolVersionException(\"None of the AMQP versions defines \" +" + + CR + " \"field \\\"" + field.getName() + + "\\\" as domain \\\"\" + classObj.getName() + \"\\\".\");" + CR); + sb.append(indent + "}" + CR); + sb.append(CR); + return sb.toString(); + } + + protected String generateMbParamList(String codeType, AmqpField field, + AmqpVersionSet versionSet, int indentSize, int tabSize, boolean nextFlag) + { + return Utils.createSpaces(indentSize) + codeType + " " + field.getName() + + (nextFlag ? "," : "") + " // AMQP version(s): " + versionSet + CR; + } + + + protected String generateMbPassedParamList(String codeType, AmqpField field, + AmqpVersionSet versionSet, int indentSize, int tabSize, boolean nextFlag) + { + return Utils.createSpaces(indentSize) + field.getName() + + (nextFlag ? "," : "") + " // AMQP version(s): " + versionSet + CR; + } + + + protected String generateMbMangledParamList(AmqpField field, int indentSize, + int tabSize, boolean nextFlag) + { + StringBuffer sb = new StringBuffer(); + Iterator<String> dItr = field.getDomainMap().keySet().iterator(); + int domainCntr = 0; + while (dItr.hasNext()) + { + String domainName = dItr.next(); + AmqpVersionSet versionSet = field.getDomainMap().get(domainName); + String codeType = getGeneratedType(domainName, versionSet.first()); + sb.append(Utils.createSpaces(indentSize) + codeType + " " + field.getName() + "_" + + (domainCntr++) + (nextFlag ? "," : "") + " // AMQP version(s): " + + versionSet + CR); + } + return sb.toString(); + } + + protected String generateMbMangledPassedParamList(AmqpField field, int indentSize, + int tabSize, boolean nextFlag) + { + StringBuffer sb = new StringBuffer(); + Iterator<String> dItr = field.getDomainMap().keySet().iterator(); + int domainCntr = 0; + while (dItr.hasNext()) + { + String domainName = dItr.next(); + AmqpVersionSet versionSet = field.getDomainMap().get(domainName); + sb.append(Utils.createSpaces(indentSize) + field.getName() + "_" + + (domainCntr++) + (nextFlag ? "," : "") + " // AMQP version(s): " + + versionSet + CR); + } + return sb.toString(); + } + + + protected String generateMbBodyInit(String codeType, AmqpField field, + AmqpVersionSet versionSet, int indentSize, int tabSize, boolean nextFlag) + { + return Utils.createSpaces(indentSize) + "this." + field.getName() + " = " + field.getName() + + ";" + CR; + } + + protected String generateMbMangledBodyInit(AmqpField field, int indentSize, + int tabSize, boolean nextFlag) + { + StringBuffer sb = new StringBuffer(); + Iterator<String> dItr = field.getDomainMap().keySet().iterator(); + int domainCntr = 0; + while (dItr.hasNext()) + { + dItr.next(); + sb.append(Utils.createSpaces(indentSize) + "this." + field.getName() + "_" + domainCntr + + " = " + field.getName() + "_" + (domainCntr++) + ";" + CR); + } + return sb.toString(); + } + + protected String generateMbFieldSize(String domainType, String fieldName, + int ordinal, int indentSize, int tabSize) + { + StringBuffer sb = new StringBuffer(); + sb.append(Utils.createSpaces(indentSize) + "size += " + + typeMap.get(domainType).size.replaceAll("#", fieldName) + + "; // " + fieldName + ": " + domainType + CR); + return sb.toString(); + } + + protected String generateMbBitArrayFieldSize(List<String> bitFieldList, + int ordinal, int indentSize, int tabSize) + { + StringBuffer sb = new StringBuffer(); + int numBytes = ((bitFieldList.size() - 1) / 8) + 1; + String comment = bitFieldList.size() == 1 ? + bitFieldList.get(0) + ": bit" : + "Combinded bits: " + bitFieldList; + sb.append(Utils.createSpaces(indentSize) + "size += " + + typeMap.get("bit").size.replaceAll("~", String.valueOf(numBytes)) + + "; // " + comment + CR); + return sb.toString(); + } + + protected String generateMbFieldEncode(String domain, String fieldName, + int ordinal, int indentSize, int tabSize) + { + StringBuffer sb = new StringBuffer(); + sb.append(Utils.createSpaces(indentSize) + + typeMap.get(domain).encodeExpression.replaceAll("#", fieldName) + + "; // " + fieldName + ": " + domain + CR); + return sb.toString(); + } + + protected String generateMbBitFieldEncode(List<String> bitFieldList, + int ordinal, int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + + StringBuilder sb = new StringBuilder(); + int i = 0; + while (i < bitFieldList.size()) + { + + StringBuilder line = new StringBuilder(); + + for (int j = 0; i < bitFieldList.size() && j < 8; i++, j++) + { + if (j != 0) + { + line.append(", "); + } + line.append(bitFieldList.get(i)); + } + + sb.append(indent + + typeMap.get("bit").encodeExpression.replaceAll("#", line.toString()) + ";" + CR); + } + return sb.toString(); + } + + protected String generateMbFieldDecode(String domain, String fieldName, + int ordinal, int indentSize, int tabSize) + { + StringBuffer sb = new StringBuffer(); + sb.append(Utils.createSpaces(indentSize) + + typeMap.get(domain).decodeExpression.replaceAll("#", fieldName) + + "; // " + fieldName + ": " + domain + CR); + return sb.toString(); + } + + protected String generateMbBitFieldDecode(List<String> bitFieldList, + int ordinal, int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + + StringBuilder sb = new StringBuilder(indent); + sb.append("byte packedValue;"); + sb.append(CR); + + // RG HERE! + + int i = 0; + while (i < bitFieldList.size()) + { + sb.append(indent + "packedValue = EncodingUtils.readByte(buffer);" + CR); + + for (int j = 0; i < bitFieldList.size() && j < 8; i++, j++) + { + sb.append(indent + bitFieldList.get(i) + " = ( packedValue & (byte) (1 << " + j + ") ) != 0;" + CR); + } + } + return sb.toString(); + } + + protected String generateMbFieldToString(String domain, String fieldName, + int ordinal, int indentSize, int tabSize) + { + StringBuffer sb = new StringBuffer(); + sb.append(Utils.createSpaces(indentSize) + + "buf.append(\" " + fieldName + ": \" + " + fieldName + ");" + CR); + return sb.toString(); + } + + protected String generateMbBitFieldToString(List<String> bitFieldList, + int ordinal, int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < bitFieldList.size(); i++) + { + String bitFieldName = bitFieldList.get(i); + sb.append(indent + "buf.append(\" " + bitFieldName + ": \" + " + bitFieldName + + ");" + CR); + } + return sb.toString(); + } + + // Methods for PropertyContentHeader classes + + protected String generatePchClearMethod(String codeType, AmqpField field, + AmqpVersionSet versionSet, int indentSize, int tabSize, boolean nextFlag) + { + // This is one case where the ordinal info is the only significant factor, + // the domain info plays no part. Defer to the mangled version; the code would be + // identical anyway... + return generatePchMangledClearMethod(field, indentSize, tabSize, nextFlag); + } + + protected String generatePchMangledClearMethod(AmqpField field, int indentSize, + int tabSize, boolean nextFlag) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + StringBuffer sb = new StringBuffer(); + sb.append(indent + "public void clear" + Utils.firstUpper(field.getName()) + + "()" + CR); + sb.append(indent + "{" + CR); + + // If there is more than one ordinal for this field or the ordinal does not + // apply to all known versions, then we need to generate version checks so + // we know which fieldProperty to clear. + if (field.getOrdinalMap().size() == 1 && + field.getOrdinalMap().get(field.getOrdinalMap().firstKey()).size() == field.getVersionSet().size()) + { + int ordinal = field.getOrdinalMap().firstKey(); + sb.append(indent + tab + "clearEncodedForm();" + CR); + sb.append(indent + tab + "propertyFlags[" + ordinal + "] = false;" + CR); + } + else + { + Iterator<Integer> oItr = field.getOrdinalMap().keySet().iterator(); + while (oItr.hasNext()) + { + int ordinal = oItr.next(); + AmqpVersionSet versionSet = field.getOrdinalMap().get(ordinal); + sb.append(indent + tab); + if (ordinal != field.getOrdinalMap().firstKey()) + { + sb.append("else "); + } + sb.append("if ("); + sb.append(generateVersionCheck(versionSet)); + sb.append(")" + CR); + sb.append(indent + tab + "{" + CR); + sb.append(indent + tab + tab + "clearEncodedForm();" + CR); + sb.append(indent + tab + tab + "propertyFlags[" + ordinal + "] = false;" + CR); + sb.append(indent + tab + "}" + CR); + } + } + sb.append(indent + "}" + CR); + sb.append(CR); + return sb.toString(); + } + + protected String generatePchGetMethod(String codeType, AmqpField field, + AmqpVersionSet versionSet, int indentSize, int tabSize, boolean nextFlag) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + StringBuffer sb = new StringBuffer(indent + "public " + codeType + " get" + + Utils.firstUpper(field.getName()) + "()" + CR); + sb.append(indent + "{" + CR); + sb.append(indent + tab + "decodeIfNecessary();" + CR); + sb.append(indent + tab + "return " + field.getName() + ";" + CR); + sb.append(indent + "}" + CR); + sb.append(CR); + return sb.toString(); + } + + protected String generatePchMangledGetMethod(AmqpField field, int indentSize, + int tabSize, boolean nextFlag) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + StringBuffer sb = new StringBuffer(indent + "public <T> T get" + + Utils.firstUpper(field.getName()) + + "(Class<T> classObj) throws AMQProtocolVersionException" + CR); + sb.append(indent + "{" + CR); + Iterator<String> dItr = field.getDomainMap().keySet().iterator(); + int domainCntr = 0; + while (dItr.hasNext()) + { + String domainName = dItr.next(); + AmqpVersionSet versionSet = field.getDomainMap().get(domainName); + String codeType = getGeneratedType(domainName, versionSet.first()); + sb.append(indent + tab + "if (classObj.equals(" + codeType + + ".class)) // AMQP Version(s): " + versionSet + CR); + sb.append(indent + tab + "{" + CR); + sb.append(indent + tab + tab + "decodeIfNecessary();" + CR); + sb.append(indent + tab + tab + "return (T)(Object)" + field.getName() + "_" + + (domainCntr++) + ";" + CR); + sb.append(indent + tab + "}" + CR); + } + sb.append(indent + tab + + "throw new AMQProtocolVersionException(\"None of the AMQP versions defines \" +" + + CR + " \"field \\\"" + field.getName() + + "\\\" as domain \\\"\" + classObj.getName() + \"\\\".\");" + CR); + sb.append(indent + "}" + CR); + sb.append(CR); + return sb.toString(); + } + + protected String generatePchSetMethod(String codeType, AmqpField field, + AmqpVersionSet versionSet, int indentSize, int tabSize, boolean nextFlag) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + StringBuffer sb = new StringBuffer(); + sb.append(indent + "public void set" + Utils.firstUpper(field.getName()) + + "(" + codeType + " " + field.getName() + ")" + CR); + sb.append(indent + "{" + CR); + + // If there is more than one ordinal for this field or the ordinal does not + // apply to all known versions, then we need to generate version checks so + // we know which fieldProperty to clear. + if (field.getOrdinalMap().size() == 1 && + field.getOrdinalMap().get(field.getOrdinalMap().firstKey()).size() == field.getVersionSet().size()) + { + int ordinal = field.getOrdinalMap().firstKey(); + sb.append(indent + tab + "clearEncodedForm();" + CR); + sb.append(indent + tab + "propertyFlags[" + ordinal + "] = true;" + CR); + sb.append(indent + tab + "this." + field.getName() + " = " + field.getName() + ";" + CR); + } + else + { + Iterator<Integer> oItr = field.getOrdinalMap().keySet().iterator(); + while (oItr.hasNext()) + { + int ordinal = oItr.next(); + AmqpVersionSet oVersionSet = field.getOrdinalMap().get(ordinal); + sb.append(indent + tab); + if (ordinal != field.getOrdinalMap().firstKey()) + { + sb.append("else "); + } + sb.append("if ("); + sb.append(generateVersionCheck(oVersionSet)); + sb.append(")" + CR); + sb.append(indent + tab + "{" + CR); + sb.append(indent + tab + tab + "clearEncodedForm();" + CR); + sb.append(indent + tab + tab + "propertyFlags[" + ordinal + "] = true;" + CR); + sb.append(indent + tab + tab + "this." + field.getName() + " = " + field.getName() + ";" + CR); + sb.append(indent + tab + "}" + CR); + } + } + sb.append(indent + "}" + CR); + sb.append(CR); + return sb.toString(); + } + + protected String generatePchMangledSetMethod(AmqpField field, int indentSize, + int tabSize, boolean nextFlag) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + StringBuffer sb = new StringBuffer(); + + Iterator<String> dItr = field.getDomainMap().keySet().iterator(); + int domainCntr = 0; + while (dItr.hasNext()) + { + String domainName = dItr.next(); + AmqpVersionSet versionSet = field.getDomainMap().get(domainName); + String codeType = getGeneratedType(domainName, versionSet.first()); + + // Find ordinal with matching version + AmqpVersionSet commonVersionSet = new AmqpVersionSet(); + Iterator<Integer> oItr = field.getOrdinalMap().keySet().iterator(); + while (oItr.hasNext()) + { + int ordinal = oItr.next(); + AmqpVersionSet oVersionSet = field.getOrdinalMap().get(ordinal); + Iterator<AmqpVersion> vItr = oVersionSet.iterator(); + boolean first = true; + while (vItr.hasNext()) + { + AmqpVersion thisVersion = vItr.next(); + if (versionSet.contains(thisVersion)) + { + commonVersionSet.add(thisVersion); + } + } + if (!commonVersionSet.isEmpty()) + { + sb.append(indent + "public void set" + Utils.firstUpper(field.getName()) + + "(" + codeType + " " + field.getName() + ")" + CR); + sb.append(indent + "{" + CR); + sb.append(indent + tab); + if (!first) + { + sb.append("else "); + } + sb.append("if ("); + sb.append(generateVersionCheck(commonVersionSet)); + sb.append(")" + CR); + sb.append(indent + tab + "{" + CR); + sb.append(indent + tab + tab + "clearEncodedForm();" + CR); + sb.append(indent + tab + tab + "propertyFlags[" + ordinal + "] = true;" + CR); + sb.append(indent + tab + tab + "this." + field.getName() + "_" + (domainCntr++) + + " = " + field.getName() + ";" + CR); + sb.append(indent + tab + "}" + CR); + sb.append(indent + "}" + CR); + sb.append(CR); + first = false; + } + } + } + return sb.toString(); + } + + protected String generatePchFieldSize(String domainType, String fieldName, + int ordinal, int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(indent + "if (propertyFlags[" + ordinal + "]) // " + + fieldName + ": " + domainType + CR); + sb.append(indent + Utils.createSpaces(tabSize) + "size += " + + typeMap.get(domainType).size.replaceAll("#", fieldName) + ";" + CR); + sb.append(CR); + return sb.toString(); + } + + protected String generatePchBitArrayFieldSize(List<String> bitFieldList, + int ordinal, int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + String comment = bitFieldList.size() == 1 ? + bitFieldList.get(0) + ": bit" : + "Combinded bits: " + bitFieldList; + StringBuffer sb = new StringBuffer(); + + if (bitFieldList.size() == 1) // single bit + { + sb.append(indent + "if (propertyFlags[" + (ordinal - 1) + "]) // " + comment + CR); + sb.append(indent + tab + "size += " + + typeMap.get("bit").size.replaceAll("~", "1") + ";" + CR); + } + else // multiple bits - up to 8 are combined into one byte + { + String bitCntrName = "bitCntr_" + ordinal; + int startOrdinal = ordinal - bitFieldList.size(); + sb.append(indent + "// " + comment + CR); + sb.append(indent + "int " + bitCntrName + " = 0;" + CR); + sb.append(indent + "for (int i=" + startOrdinal + "; i<" + ordinal + "; i++)" + CR); + sb.append(indent + "{" + CR); + sb.append(indent + tab + "if (propertyFlags[i])" + CR); + sb.append(indent + tab + tab + bitCntrName + "++;" + CR); + sb.append(indent + "}" + CR); + sb.append(indent + "size += " + + typeMap.get("bit").size.replaceAll("~", bitCntrName + + " > 0 ? ((" + bitCntrName + " - 1) / 8) + 1 : 0") + ";" + CR); + } + sb.append(CR); + return sb.toString(); + } + + protected String generatePchFieldEncode(String domainType, String fieldName, + int ordinal, int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + sb.append(indent + "if (propertyFlags[" + ordinal + "]) // " + fieldName + ": " + + domainType + CR); + sb.append(indent + Utils.createSpaces(tabSize) + + typeMap.get(domainType).encodeExpression.replaceAll("#", fieldName) + ";" + CR); + sb.append(CR); + return sb.toString(); + } + + protected String generatePchBitFieldEncode(List<String> bitFieldList, + int ordinal, int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + String comment = bitFieldList.size() == 1 ? + bitFieldList.get(0) + ": bit" : + "Combinded bits: " + bitFieldList; + StringBuffer sb = new StringBuffer(); + + if (bitFieldList.size() == 1) // single bit + { + sb.append(indent + "if (propertyFlags[" + (ordinal - 1) + "]) // " + + bitFieldList.get(0) + ": bit" + CR); + sb.append(indent + tab + typeMap.get("bit").encodeExpression.replaceAll("#", + "new boolean[] {" + bitFieldList.get(0) + "}") + ";" + CR); + } + else // multiple bits - up to 8 are combined into one byte + { + int startOrdinal = ordinal - bitFieldList.size(); + String bitCntrName = "bitCntr" + startOrdinal; + sb.append(indent + "// " + comment + CR); + sb.append(indent + "int " + bitCntrName + " = 0;" + CR); + sb.append(indent + "for (int i=" + startOrdinal + "; i<=" + (ordinal - 1) + "; i++)" + CR); + sb.append(indent + "{" + CR); + sb.append(indent + tab + "if (propertyFlags[i])" + CR); + sb.append(indent + tab + tab + bitCntrName + "++;" + CR); + sb.append(indent + "}" + CR); + sb.append(indent + "if (" + bitCntrName + " > 0) // Are any of the property bits set?" + CR); + sb.append(indent + "{" + CR); + sb.append(indent + tab + "boolean[] fullBitArray = new boolean[] { "); + for (int i = 0; i < bitFieldList.size(); i++) + { + if (i != 0) + { + sb.append(", "); + } + sb.append(bitFieldList.get(i)); + } + sb.append(" };" + CR); + sb.append(indent + tab + "boolean[] flaggedBitArray = new boolean[" + bitCntrName + + "];" + CR); + sb.append(indent + tab + bitCntrName + " = 0;" + CR); + sb.append(indent + tab + "for (int i=" + startOrdinal + "; i<=" + (ordinal - 1) + + "; i++)" + CR); + sb.append(indent + tab + "{" + CR); + sb.append(indent + tab + tab + "if (propertyFlags[i])" + CR); + sb.append(indent + tab + tab + tab + "flaggedBitArray[" + bitCntrName + + "++] = fullBitArray[i];" + CR); + sb.append(indent + tab + "}" + CR); + sb.append(indent + tab + typeMap.get("bit").encodeExpression.replaceAll("#", + "flaggedBitArray") + ";" + CR); + sb.append(indent + "}" + CR); + } + sb.append(CR); + return sb.toString(); + } + + protected String generatePchFieldDecode(String domainType, String fieldName, + int ordinal, int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + sb.append(indent + "if (propertyFlags[" + ordinal + "]) // " + fieldName + ": " + + domainType + CR); + sb.append(indent + Utils.createSpaces(tabSize) + + typeMap.get(domainType).decodeExpression.replaceAll("#", fieldName) + ";" + CR); + sb.append(CR); + return sb.toString(); + } + + protected String generatePchBitFieldDecode(List<String> bitFieldList, + int ordinal, int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + String comment = bitFieldList.size() == 1 ? + bitFieldList.get(0) + ": bit" : + "Combinded bits: " + bitFieldList; + StringBuffer sb = new StringBuffer(); + + if (bitFieldList.size() == 1) // single bit + { + sb.append(indent + "if (propertyFlags[" + (ordinal - 1) + "]) // " + + bitFieldList.get(0) + ": bit" + CR); + sb.append(indent + "{" + CR); + sb.append(indent + tab + typeMap.get("bit").decodeExpression.replaceAll("#", + "boolean[] flaggedBitArray") + ";" + CR); + sb.append(indent + tab + bitFieldList.get(0) + " = flaggedBitArray[0];" + CR); + sb.append(indent + "}" + CR); + } + else // multiple bits - up to 8 are combined into one byte + { + int startOrdinal = ordinal - bitFieldList.size(); + String bitCntr = "bitCntr" + startOrdinal; + sb.append(indent + "// " + comment + CR); + sb.append(indent + "int " + bitCntr + " = 0;" + CR); + sb.append(indent + "for (int i=" + startOrdinal + "; i<=" + (ordinal - 1) + "; i++)" + CR); + sb.append(indent + "{" + CR); + sb.append(indent + tab + "if (propertyFlags[i])" + CR); + sb.append(indent + tab + tab + bitCntr + "++;" + CR); + sb.append(indent + "}" + CR); + sb.append(indent + "if (" + bitCntr + " > 0) // Are any of the property bits set?" + CR); + sb.append(indent + "{" + CR); + sb.append(indent + tab + typeMap.get("bit").decodeExpression.replaceAll("#", + "boolean[] flaggedBitArray") + ";" + CR); + sb.append(indent + tab + bitCntr + " = 0;" + CR); + for (int i = 0; i < bitFieldList.size(); i++) + { + sb.append(indent + tab + "if (propertyFlags[" + (startOrdinal + i) + "])" + CR); + sb.append(indent + tab + tab + bitFieldList.get(i) + " = flaggedBitArray[" + + bitCntr + "++];" + CR); + } + sb.append(indent + "}" + CR); + } + + sb.append(CR); + return sb.toString(); + } + + protected String generatePchGetPropertyFlags(String domainType, String fieldName, + int ordinal, int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + StringBuffer sb = new StringBuffer(); + int word = ordinal / 15; + int bit = 15 - (ordinal % 15); + sb.append(indent + "if (propertyFlags[" + ordinal + "]) // " + fieldName + ": " + + domainType + CR); + sb.append(indent + tab + "compactPropertyFlags[" + word + "] |= (1 << " + + bit + ");" + CR); + sb.append(CR); + return sb.toString(); + } + + protected String generatePchBitGetPropertyFlags(List<String> bitFieldList, + int ordinal, int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + StringBuffer sb = new StringBuffer(); + int startOrdinal = ordinal - bitFieldList.size(); + + for (int i = 0; i < bitFieldList.size(); i++) + { + int thisOrdinal = startOrdinal + i; + int word = thisOrdinal / 15; + int bit = 15 - (thisOrdinal % 15); + sb.append(indent + "if (propertyFlags[" + thisOrdinal + "])" + CR); + sb.append(indent + tab + "compactPropertyFlags[" + word + + "] |= (1 << " + bit + ");" + CR); + } + + sb.append(CR); + return sb.toString(); + } + + protected String generatePchSetPropertyFlags(String domainType, String fieldName, + int ordinal, int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + int word = ordinal / 15; + int bit = 15 - (ordinal % 15); + sb.append(indent + "propertyFlags[" + ordinal + "] = (compactPropertyFlags[" + + word + "] & (1 << " + bit + ")) > 0;" + CR); + return sb.toString(); + } + + protected String generatePchBitSetPropertyFlags(List<String> bitFieldList, + int ordinal, int indentSize, int tabSize) + { + String indent = Utils.createSpaces(indentSize); + StringBuffer sb = new StringBuffer(); + int startOrdinal = ordinal - bitFieldList.size(); + + for (int i = 0; i < bitFieldList.size(); i++) + { + int thisOrdinal = startOrdinal + i; + int word = thisOrdinal / 15; + int bit = 15 - (thisOrdinal % 15); + sb.append(indent + "propertyFlags[" + thisOrdinal + "] = (compactPropertyFlags[" + + word + "] & (1 << " + bit + ")) > 0;" + CR); + } + return sb.toString(); + } + + private String generatePchPropertyFlagsDeclare() + { + return "private boolean[] propertyFlags;"; + } + + private String generatePchPropertyFlagsInitializer(int totNumFields) + { + return "propertyFlags = new boolean[" + totNumFields + "];"; + } + + private String generatePchCompactPropertyFlagsInitializer(AmqpClass thisClass, int indentSize, + int tabSize) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + StringBuffer sb = new StringBuffer(); + Iterator<AmqpVersion> vItr = thisClass.getVersionSet().iterator(); + while (vItr.hasNext()) + { + AmqpVersion version = vItr.next(); + int numBytes = ((thisClass.getFieldMap().getNumFields(version) - 1) / 15) + 1; + + sb.append(indent); + if (!version.equals(thisClass.getVersionSet().first())) + { + sb.append("else "); + } + sb.append("if ( major == " + version.getMajor() + " && minor == " + + version.getMinor() + " )" + CR); + sb.append(indent + tab + "compactPropertyFlags = new int[] { "); + for (int i = 0; i < numBytes; i++) + { + if (i != 0) + { + sb.append(", "); + } + sb.append(i < numBytes - 1 ? "1" : "0"); // Set the "continue" flag where required + } + sb.append(" };" + CR); + } + return sb.toString(); + } + + private String generatePchCompactPropertyFlagsCheck(AmqpClass thisClass, int indentSize, + int tabSize) + { + String indent = Utils.createSpaces(indentSize); + String tab = Utils.createSpaces(tabSize); + StringBuffer sb = new StringBuffer(); + Iterator<AmqpVersion> vItr = thisClass.getVersionSet().iterator(); + while (vItr.hasNext()) + { + AmqpVersion version = vItr.next(); + int numFields = thisClass.getFieldMap().getNumFields(version); + int numBytes = ((numFields - 1) / 15) + 1; + + sb.append(indent); + if (!version.equals(thisClass.getVersionSet().first())) + { + sb.append("else "); + } + sb.append("if ( major == " + version.getMajor() + " && minor == " + + version.getMinor() + " && compactPropertyFlags.length != " + numBytes + " )" + CR); + sb.append(indent + tab + + "throw new AMQProtocolVersionException(\"Property flag array size mismatch:\" +" + CR); + sb.append(indent + tab + tab + "\"(Size found: \" + compactPropertyFlags.length +" + CR); + sb.append(indent + tab + tab + "\") Version " + version + " has " + numFields + + " fields which requires an int array of size " + numBytes + ".\");" + CR); + } + return sb.toString(); + } + + private String generateVersionCheck(AmqpVersionSet v) + { + StringBuffer sb = new StringBuffer(); + AmqpVersion[] versionArray = new AmqpVersion[v.size()]; + v.toArray(versionArray); + for (int i = 0; i < versionArray.length; i++) + { + if (i != 0) + { + sb.append(" || "); + } + if (versionArray.length > 1) + { + sb.append("("); + } + sb.append("major == (byte)" + versionArray[i].getMajor() + " && minor == (byte)" + + versionArray[i].getMinor()); + if (versionArray.length > 1) + { + sb.append(")"); + } + } + return sb.toString(); + } + + private String camelCaseName(String name, boolean upperFirstFlag) + { + StringBuffer ccn = new StringBuffer(); + String[] toks = name.split("[-_.\\ ]"); + for (int i = 0; i < toks.length; i++) + { + StringBuffer b = new StringBuffer(toks[i]); + if (upperFirstFlag || i > 0) + { + b.setCharAt(0, Character.toUpperCase(toks[i].charAt(0))); + } + ccn.append(b); + } + return ccn.toString(); + } + + + private String upperCaseName(String name) + { + StringBuffer ccn = new StringBuffer(); + String[] toks = name.split("[-_.\\ ]"); + for (int i = 0; i < toks.length; i++) + { + if (i != 0) + { + ccn.append('_'); + } + ccn.append(toks[i].toUpperCase()); + + + } + return ccn.toString(); + } + + + public static Factory<JavaGenerator> _factoryInstance = new Factory<JavaGenerator>() + { + + public JavaGenerator newInstance() + { + return new JavaGenerator(); + } + }; + + public static Factory<JavaGenerator> getFactory() + { + return _factoryInstance; + } + + + void processModelTemplate(NamedTemplate template, AmqpVersion version) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + void processClassTemplate(NamedTemplate template, AmqpClass amqpClass, AmqpVersion version) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + void processMethodTemplate(NamedTemplate template, AmqpClass amqpClass, AmqpMethod amqpMethod, AmqpVersion version) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + void processFieldTemplate(NamedTemplate template, AmqpClass amqpClass, AmqpMethod amqpMethod, AmqpField amqpField, AmqpVersion version) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/LanguageConverter.java b/qpid/gentools/src/org/apache/qpid/gentools/LanguageConverter.java new file mode 100644 index 0000000000..5e692d86e7 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/LanguageConverter.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.gentools; + +public interface LanguageConverter +{ + +// public AmqpDomainMap getDomainMap(); +// public AmqpConstantSet getConstantSet(); +// public AmqpModel getModel(); + + // + public String prepareClassName(String className); + + public String prepareMethodName(String methodName); + + public String prepareDomainName(String domainName); + + public String getDomainType(String domainName, AmqpVersion version); + + public String getGeneratedType(String domainName, AmqpVersion version); + + public String prepareConstantName(String constantName); +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/Main.java b/qpid/gentools/src/org/apache/qpid/gentools/Main.java new file mode 100644 index 0000000000..c0584f7ca7 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/Main.java @@ -0,0 +1,301 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.gentools; + +import org.apache.velocity.app.Velocity; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Properties; + +public class Main +{ + private static final String DEFAULT_OUTPUT_DIR = ".." + Utils.FILE_SEPARATOR + "gen"; + private static final String DEFAULT_TEMPLATE_DIR_BASE = ".." + Utils.FILE_SEPARATOR; + + private enum GeneratedLanguage + { + CPP(".cpp", CppGenerator.getFactory()), + DOTNET(".net", DotnetGenerator.getFactory()), + JAVA(".java", JavaGenerator.getFactory()); + + private final String _suffix; + private final Generator.Factory _factory; + + + private final String _defaultTemplateDirectory; + + GeneratedLanguage(String suffix, Generator.Factory factory) + { + _suffix = suffix; + _factory = factory; + _defaultTemplateDirectory = DEFAULT_TEMPLATE_DIR_BASE + "templ" + _suffix; + } + + public String getSuffix() + { + return _suffix; + } + + public Generator newGenerator() + { + return _factory.newInstance(); + } + + public String getDefaultTemplateDirectory() + { + return _defaultTemplateDirectory; + } + } + + private Generator generator; + + private String outDir; + private String tmplDir; + private GeneratedLanguage _generatorLang; + private ArrayList<String> xmlFiles; + + public Main() + { + xmlFiles = new ArrayList<String>(); + } + + public void run(String[] args) + throws Exception, + SAXException, + AmqpParseException, + AmqpTypeMappingException, + AmqpTemplateException, + TargetDirectoryException, + IllegalAccessException, + InvocationTargetException, ParserConfigurationException + { + + // 0. Initialize + outDir = DEFAULT_OUTPUT_DIR; + tmplDir = null; + _generatorLang = GeneratedLanguage.CPP; // Default generation language + xmlFiles.clear(); + processArgs(args); + + if (tmplDir == null) + { + tmplDir = _generatorLang.getDefaultTemplateDirectory(); + } + + + generator = _generatorLang.newGenerator(); + generator.setTemplateDirectory(tmplDir); + generator.setOutputDirectory(outDir); + + // 1. Suck in all the XML spec files provided on the command line + analyzeXML(); + + Properties p = new Properties(); + p.setProperty("file.resource.loader.path", tmplDir); + + Velocity.init(p); + + // 2. Load up all templates + generator.initializeTemplates(); + + // 3. Generate output + generator.generate(); + + System.out.println("Files generated: " + generator.getNumberGeneratedFiles()); + System.out.println("Done."); + } + + private void processArgs(String[] args) + { + // Crude but simple... + for (int i = 0; i < args.length; i++) + { + String arg = args[i]; + if (arg.charAt(0) == '-') + { + switch (arg.charAt(1)) + { + case'c': + case'C': + _generatorLang = GeneratedLanguage.CPP; + break; + case'j': + case'J': + _generatorLang = GeneratedLanguage.JAVA; + break; + case'n': + case'N': + _generatorLang = GeneratedLanguage.DOTNET; + break; + case'o': + case'O': + if (++i < args.length) + { + outDir = args[i]; + } + break; + case't': + case'T': + if (++i < args.length) + { + tmplDir = args[i]; + } + break; + } + } + else + { + xmlFiles.add(args[i]); + } + } + } + + private void analyzeXML() + throws IOException, SAXException, AmqpParseException, AmqpTypeMappingException, ParserConfigurationException + { + DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + + System.out.println("XML files: " + xmlFiles); + for (String filename : xmlFiles) + { + File f = new File(filename); + if (f.exists()) + { + // 1a. Initialize dom + System.out.print(" \"" + filename + "\":"); + Document doc = docBuilder.parse(new File(filename)); + Node amqpNode = Utils.findChild(doc, Utils.ELEMENT_AMQP); + + // 1b. Extract version (major and minor) from the XML file + int major = Utils.getNamedIntegerAttribute(amqpNode, Utils.ATTRIBUTE_MAJOR); + int minor = Utils.getNamedIntegerAttribute(amqpNode, Utils.ATTRIBUTE_MINOR); + AmqpVersion version = new AmqpVersion(major, minor); + System.out.println(" Found version " + version.toString() + "."); + generator.addVersion(version); + generator.addFromNode(amqpNode, version); + + + } + else + { + System.err.println("ERROR: AMQP XML file \"" + filename + "\" not found."); + } + } +// *** DEBUG INFO *** Uncomment bits from this block to see lots of stuff.... +// System.out.println(); +// System.out.println("*** Debug output ***"); +// System.out.println(); +// versionSet.print(System.out, 0, 2); // List of loaded versions +// System.out.println(); +// constants.print(System.out, 0, 2); // List of constants +// System.out.println(); +// domainMap.print(System.out, 0, 2); // List of domains +// System.out.println(); +// model.print(System.out, 0, 2); // Internal version map model +// System.out.println(); +// System.out.println("*** End debug output ***"); +// System.out.println(); + } + + public static void main(String[] args) + { + int exitCode = 1; + // TODO: This is a simple and klunky way of hangling command-line args, and could be improved upon. + if (args.length < 2) + { + usage(); + } + else + { + try + { + new Main().run(args); + exitCode = 0; + } + catch (IOException e) + { + e.printStackTrace(); + } + catch (ParserConfigurationException e) + { + e.printStackTrace(); + } + catch (SAXException e) + { + e.printStackTrace(); + } + catch (AmqpParseException e) + { + e.printStackTrace(); + } + catch (AmqpTypeMappingException e) + { + e.printStackTrace(); + } + catch (AmqpTemplateException e) + { + e.printStackTrace(); + } + catch (TargetDirectoryException e) + { + e.printStackTrace(); + } + catch (IllegalAccessException e) + { + e.printStackTrace(); + } + catch (InvocationTargetException e) + { + e.printStackTrace(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + System.exit(exitCode); + } + + public static void usage() + { + System.out.println("AMQP XML generator v.0.0"); + System.out.println("Usage: Main -c|-j [-o outDir] [-t tmplDir] XMLfile [XMLfile ...]"); + System.out.println(" where -c: Generate C++."); + System.out.println(" -j: Generate Java."); + System.out.println(" -n: Generate .NET."); + System.out.println(" -o outDir: Use outDir as the output dir (default=\"" + DEFAULT_OUTPUT_DIR + "\")."); + System.out.println(" -t tmplDir: Find templates in tmplDir."); + System.out.println(" Defaults: \"" + GeneratedLanguage.CPP.getDefaultTemplateDirectory() + "\" for C++;"); + System.out.println(" \"" + GeneratedLanguage.JAVA.getDefaultTemplateDirectory() + "\" for java.;"); + System.out.println(" \"" + GeneratedLanguage.DOTNET.getDefaultTemplateDirectory() + "\" for .NET."); + System.out.println(" XMLfile is a space-separated list of AMQP XML files to be parsed."); + } + +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/MangledGenerateMethod.java b/qpid/gentools/src/org/apache/qpid/gentools/MangledGenerateMethod.java new file mode 100644 index 0000000000..ffeefed900 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/MangledGenerateMethod.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.gentools; + +public interface MangledGenerateMethod +{ + String generate(AmqpField field, int indentSize, int tabSize, boolean notLast); +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/NodeAware.java b/qpid/gentools/src/org/apache/qpid/gentools/NodeAware.java new file mode 100644 index 0000000000..f832da75ad --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/NodeAware.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.gentools; + +import org.w3c.dom.Node; + +/** + * @author kpvdr + * Interface allowing the addition of elements from a node in the + * DOM of the AMQP specification. It is used by each of the model + * elements in a recursive fashion to build the model. + */ +public interface NodeAware +{ + /** + * Add a model element from the current DOM node. All model elements must implement + * this interface. If the node contains children that are also a part of the model, + * then this method is called on new instances of those model elements. + * + * @param n Node from which the current model element is to be added. + * @param o Ordinal value of the current model elemet. + * @param v Verion of the DOM from which the node comes. + * @throws AmqpParseException + * @throws AmqpTypeMappingException + * @returns true if a node was added, false if not + */ + public boolean addFromNode(Node n, int o, AmqpVersion v) + throws AmqpParseException, AmqpTypeMappingException; +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/Printable.java b/qpid/gentools/src/org/apache/qpid/gentools/Printable.java new file mode 100644 index 0000000000..aa13df7b68 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/Printable.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.gentools; + +import java.io.PrintStream; + +public interface Printable +{ + public void print(PrintStream out, int marginSize, int tabSize); +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/SingleVersionClass.java b/qpid/gentools/src/org/apache/qpid/gentools/SingleVersionClass.java new file mode 100644 index 0000000000..8e1af1c551 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/SingleVersionClass.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.gentools; + +import java.util.Map; +import java.util.List; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Map.Entry; + + +public class SingleVersionClass +{ + private final int _classId; + + + private final AmqpClass _amqpClass; + private final AmqpVersion _amqpVersion; + private final Generator _generator; + private final List<SingleVersionMethod> _methodList = new ArrayList<SingleVersionMethod>(); + + public SingleVersionClass(AmqpClass amqpClass, AmqpVersion amqpVersion, Generator generator) + { + _amqpClass = amqpClass; + _amqpVersion = amqpVersion; + _generator = generator; + + AmqpOrdinalVersionMap indexMap = amqpClass.getIndexMap(); + int classId = 0; + for(Entry<Integer, AmqpVersionSet> entry : indexMap.entrySet()) + { + if(entry.getValue().contains(_amqpVersion)) + { + classId = entry.getKey(); + break; + } + } + _classId = classId; + + + Collection<AmqpMethod> methods = _amqpClass.getMethodMap().values(); + + for(AmqpMethod amqpMethod : methods) + { + _methodList.add(new SingleVersionMethod(amqpMethod, _amqpVersion, _generator)); + + } + + Collections.sort(_methodList, new Comparator<SingleVersionMethod>(){ + public int compare(SingleVersionMethod method1, SingleVersionMethod method2) + { + return method1.getMethodId() - method2.getMethodId(); + } + }); + + + } + + public int getClassId() + { + return _classId; + } + + public String getName() + { + return _amqpClass.getName(); + } + + + + + + public List<SingleVersionMethod> getMethodList() + { + return _methodList; + } + + + public int getMaximumMethodId() + { + return _methodList.get(_methodList.size()-1).getMethodId(); + } +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/SingleVersionField.java b/qpid/gentools/src/org/apache/qpid/gentools/SingleVersionField.java new file mode 100644 index 0000000000..b795663d15 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/SingleVersionField.java @@ -0,0 +1,68 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.gentools; + + +public class SingleVersionField +{ + private final AmqpField _field; + private final AmqpVersion _amqpVersion; + private final Generator _generator; + + public SingleVersionField(AmqpField field, AmqpVersion amqpVersion, Generator generator) + { + _field = field; + _amqpVersion = amqpVersion; + _generator = generator; + } + + public String getName() + { + return _field.getName(); + } + + public String getDomain() + { + return _field.getDomain(_amqpVersion); + } + + + public String getDomainType() + { + return _generator.getDomainType(_field.getDomain(_amqpVersion),_amqpVersion); + } + + public String getNativeType() + { + return _generator.getNativeType(getDomainType()); + } + + public String getEncodingType() + { + return _generator.getEncodingType(getDomainType()); + } + + + public int getPosition() + { + return _field.getOrdinal(_amqpVersion); + } +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/SingleVersionMethod.java b/qpid/gentools/src/org/apache/qpid/gentools/SingleVersionMethod.java new file mode 100644 index 0000000000..59a6d9e28a --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/SingleVersionMethod.java @@ -0,0 +1,154 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.gentools; + +import java.util.Map.Entry; +import java.util.Collection; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.HashMap; + +public class SingleVersionMethod +{ + private final AmqpMethod _amqpMethod; + private final AmqpVersion _amqpVersion; + private final int _methodId; + private final List<SingleVersionField> _fieldList = new ArrayList<SingleVersionField>(); + private final Generator _generator; + private final List<ConsolidatedField> _consolidatedFields = new ArrayList<ConsolidatedField>(); + private final Map<String, ConsolidatedField> _fieldNameToConsolidatedFieldMap = new HashMap<String, ConsolidatedField>(); + + + public SingleVersionMethod(AmqpMethod amqpMethod, AmqpVersion amqpVersion, Generator generator) + { + _amqpMethod = amqpMethod; + _amqpVersion = amqpVersion; + _generator = generator; + + AmqpOrdinalVersionMap indexMap = amqpMethod.getIndexMap(); + int methodId = 0; + for(Entry<Integer, AmqpVersionSet> entry : indexMap.entrySet()) + { + if(entry.getValue().contains(_amqpVersion)) + { + methodId = entry.getKey(); + break; + } + } + _methodId = methodId; + + Collection<AmqpField> fields = _amqpMethod.getFieldMap().values(); + + for(AmqpField field : fields) + { + _fieldList.add(new SingleVersionField(field, _amqpVersion, _generator)); + + } + + Collections.sort(_fieldList, new Comparator<SingleVersionField>(){ + public int compare(SingleVersionField field1, SingleVersionField field2) + { + return field1.getPosition() - field2.getPosition(); + } + }); + + + + ConsolidatedField lastField = null; + int bitfieldNum = 0; + for(SingleVersionField field : _fieldList) + { + String domainType = field.getDomainType(); + if(!domainType.equals("bit")) + { + lastField = new ConsolidatedField(_generator, + field.getName(), + field.getDomainType()); + _consolidatedFields.add(lastField); + } + else if(lastField == null || !lastField.getType().equals("bitfield")) + { + lastField = new ConsolidatedField(_generator, + domainType.equals("bit") ? "bitfield"+bitfieldNum++ : field.getName(), + domainType.equals("bit") ? "bitfield" : field.getDomainType(), + field.getName()); + _consolidatedFields.add(lastField); + } + else + { + lastField.add(field.getName()); + } + _fieldNameToConsolidatedFieldMap.put(field.getName(), lastField); + + } + } + + public int getMethodId() + { + return _methodId; + } + + public String getName() + { + return _amqpMethod.getName(); + } + + public Collection<SingleVersionField> getFieldList() + { + return Collections.unmodifiableCollection(_fieldList); + } + + public List<ConsolidatedField> getConsolidatedFields() + { + return _consolidatedFields; + } + + public String getConsolidatedFieldName(String fieldName) + { + return _fieldNameToConsolidatedFieldMap.get(fieldName).getName(); + } + + public boolean isConsolidated(String fieldName) + { + return _fieldNameToConsolidatedFieldMap.get(fieldName).isConsolidated(); + } + + public int getPositionInBitField(String fieldName) + { + return _fieldNameToConsolidatedFieldMap.get(fieldName).getPosition(fieldName); + } + + + public boolean isServerMethod() + { + return _amqpMethod.isServerMethod(_amqpVersion); + } + + + public boolean isClientMethod() + { + return _amqpMethod.isClientMethod(_amqpVersion); + } + +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/SingleVersionModel.java b/qpid/gentools/src/org/apache/qpid/gentools/SingleVersionModel.java new file mode 100644 index 0000000000..22b416e45a --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/SingleVersionModel.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.gentools; + +import java.util.Collection; +import java.util.List; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + + +public class SingleVersionModel +{ + private final AmqpModel _amqpModel; + private final AmqpVersion _amqpVersion; + private final Generator _generator; + private final List<SingleVersionClass> _classList = new ArrayList<SingleVersionClass>(); + + public SingleVersionModel(AmqpModel amqpModel, AmqpVersion amqpVersion, Generator generator) + { + _amqpModel = amqpModel; + _amqpVersion = amqpVersion; + _generator = generator; + + + Collection<AmqpClass> originalClasses = _amqpModel.getClassMap().values(); + + for(AmqpClass amqpClass : originalClasses) + { + _classList.add(new SingleVersionClass(amqpClass, _amqpVersion, _generator)); + + } + + Collections.sort(_classList, new Comparator<SingleVersionClass>(){ + public int compare(SingleVersionClass amqpClass1, SingleVersionClass amqpClass2) + { + return amqpClass1.getClassId() - amqpClass2.getClassId(); + } + }); + + + } + + public Collection<SingleVersionClass> getClassList() + { + return Collections.unmodifiableCollection(_classList); + } + + public int getMaximumClassId() + { + return _classList.get(_classList.size()-1).getClassId(); + } +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/TargetDirectoryException.java b/qpid/gentools/src/org/apache/qpid/gentools/TargetDirectoryException.java new file mode 100644 index 0000000000..39ce666288 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/TargetDirectoryException.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.gentools; + +@SuppressWarnings("serial") +public class TargetDirectoryException extends RuntimeException +{ + public TargetDirectoryException(String msg) + { + super(msg); + } +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/Utils.java b/qpid/gentools/src/org/apache/qpid/gentools/Utils.java new file mode 100644 index 0000000000..1cedaeea12 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/Utils.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.gentools; + +import org.w3c.dom.Attr; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +public class Utils +{ + public final static String FILE_SEPARATOR = System.getProperty("file.separator"); + public final static String LINE_SEPARATOR = System.getProperty("line.separator"); + + public final static String ATTRIBUTE_NAME = "name"; + public final static String ATTRIBUTE_MAJOR = "major"; + public final static String ATTRIBUTE_MINOR = "minor"; + public final static String ATTRIBUTE_INDEX = "index"; + public final static String ATTRIBUTE_LABEL = "label"; + public final static String ATTRIBUTE_SYNCHRONOUS = "synchronous"; + public final static String ATTRIBUTE_CONTENT = "content"; + public final static String ATTRIBUTE_HANDLER = "handler"; + public final static String ATTRIBUTE_DOMAIN = "domain"; + public final static String ATTRIBUTE_VALUE = "value"; + public final static String ATTRIBUTE_TYPE = "type"; // For compatibility with AMQP 8.0 + + public final static String ELEMENT_AMQP = "amqp"; + public final static String ELEMENT_CHASSIS = "chassis"; + public final static String ELEMENT_CLASS = "class"; + public final static String ELEMENT_CODEGEN = "codegen"; + public final static String ELEMENT_CONSTANT = "constant"; + public final static String ELEMENT_DOMAIN = "domain"; + public final static String ELEMENT_METHOD = "method"; + public final static String ELEMENT_FIELD = "field"; + public final static String ELEMENT_VERSION = "version"; + + // Attribute functions + + public static String getNamedAttribute(Node n, String attrName) throws AmqpParseException + { + NamedNodeMap nnm = n.getAttributes(); + if (nnm == null) + { + throw new AmqpParseException("Node \"" + n.getNodeName() + "\" has no attributes."); + } + Attr a = (Attr) nnm.getNamedItem(attrName); + if (a == null) + { + throw new AmqpParseException("Node \"" + n.getNodeName() + "\" has no attribute \"" + attrName + "\"."); + } + return a.getNodeValue(); + } + + public static int getNamedIntegerAttribute(Node n, String attrName) throws AmqpParseException + { + return Integer.parseInt(getNamedAttribute(n, attrName)); + } + + // Element functions + + public static Node findChild(Node n, String eltName) throws AmqpParseException + { + NodeList nl = n.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) + { + Node cn = nl.item(i); + if (cn.getNodeName().compareTo(eltName) == 0) + { + return cn; + } + } + throw new AmqpParseException("Node \"" + n.getNodeName() + + "\" does not contain child element \"" + eltName + "\"."); + } + + // String functions + + public static String firstUpper(String str) + { + if (!Character.isLetter(str.charAt(0)) || !Character.isLowerCase(str.charAt(0))) + { + return str; + } + StringBuffer sb = new StringBuffer(str); + sb.setCharAt(0, Character.toUpperCase(str.charAt(0))); + return sb.toString(); + } + + public static String firstLower(String str) + { + if (!Character.isUpperCase(str.charAt(0))) + { + return str; + } + StringBuffer sb = new StringBuffer(str); + sb.setCharAt(0, Character.toLowerCase(str.charAt(0))); + return sb.toString(); + } + + public static String createSpaces(int cnt) + { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < cnt; i++) + { + sb.append(' '); + } + return sb.toString(); + } + + public static boolean containsOnlyDigits(String str) + { + boolean foundNonDigit = false; + for (int i = 0; i < str.length() && !foundNonDigit; i++) + { + if (!Character.isDigit(str.charAt(i))) + { + foundNonDigit = true; + } + } + return !foundNonDigit; + } + + public static boolean containsOnlyDigitsAndDecimal(String str) + { + boolean foundNonDigit = false; + int decimalCntr = 0; + for (int i = 0; i < str.length() && !foundNonDigit && decimalCntr < 2; i++) + { + char ch = str.charAt(i); + if (!(Character.isDigit(ch) || ch == '.')) + { + foundNonDigit = true; + } + else if (ch == '.') + { + decimalCntr++; + } + } + return !foundNonDigit && decimalCntr < 2; + } +} diff --git a/qpid/gentools/src/org/apache/qpid/gentools/VersionConsistencyCheck.java b/qpid/gentools/src/org/apache/qpid/gentools/VersionConsistencyCheck.java new file mode 100644 index 0000000000..a9cdd56e88 --- /dev/null +++ b/qpid/gentools/src/org/apache/qpid/gentools/VersionConsistencyCheck.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.gentools; + +public interface VersionConsistencyCheck +{ + public boolean isVersionConsistent(AmqpVersionSet globalVersionSet); +} diff --git a/qpid/gentools/templ.cpp/method/MethodBodyClass.h.tmpl b/qpid/gentools/templ.cpp/method/MethodBodyClass.h.tmpl new file mode 100644 index 0000000000..5819a9cf9c --- /dev/null +++ b/qpid/gentools/templ.cpp/method/MethodBodyClass.h.tmpl @@ -0,0 +1,112 @@ +&{${CLASS}${METHOD}Body.h} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 ${GENERATOR} - do not modify. + * Supported AMQP versions: +%{VLIST} * ${major}-${minor} + */ + +#ifndef qpid_framing_${CLASS}${METHOD}Body__ +#define qpid_framing_${CLASS}${METHOD}Body__ + +#include <string> +#include <sstream> + +#include <amqp_types.h> +#include <AMQMethodBody.h> +#include <Buffer.h> +#include <FieldTable.h> + +namespace qpid +{ +namespace framing +{ +${version_namespace_start} + +class ${CLASS}${METHOD}Body : public AMQMethodBody +{ + // Method field declarations + +%{FLIST} ${mb_field_declaration} + + +public: + typedef boost::shared_ptr<${CLASS}${METHOD}Body> shared_ptr; + + // Constructors and destructors + +${mb_constructor_with_initializers} + + inline ${CLASS}${METHOD}Body(u_int8_t major, u_int8_t minor): AMQMethodBody(major, minor) {} + inline ${CLASS}${METHOD}Body(ProtocolVersion& version): AMQMethodBody(version) {} + virtual ~${CLASS}${METHOD}Body() {} + + // Attribute get methods + +%{FLIST} ${mb_field_get_method} + + // Helper methods + + inline void print(std::ostream& out) const + { + out << "${CLASS}${METHOD}: "; +%{FLIST} ${mb_field_print} + } + + inline u_int16_t amqpClassId() const + { + return ${CLASS_ID_INIT}; + } + + inline u_int16_t amqpMethodId() const + { + return ${METHOD_ID_INIT}; + } + + inline u_int32_t bodySize() const + { + u_int32_t size = 0; +%{FLIST} ${mb_body_size} + return size; + } + + inline void encodeContent(Buffer&${mb_buffer_param}) const + { +%{FLIST} ${mb_encode} + } + + inline void decodeContent(Buffer&${mb_buffer_param}) + { +%{FLIST} ${mb_decode} + } + +${mb_server_operation_invoke} + +}; // class ${CLASS}${METHOD}Body + +${version_namespace_end} +} // namespace framing +} // namespace qpid + +#endif + diff --git a/qpid/gentools/templ.cpp/model/AMQP_ClientOperations.h.tmpl b/qpid/gentools/templ.cpp/model/AMQP_ClientOperations.h.tmpl new file mode 100644 index 0000000000..a9fb0e0f69 --- /dev/null +++ b/qpid/gentools/templ.cpp/model/AMQP_ClientOperations.h.tmpl @@ -0,0 +1,82 @@ +&{AMQP_ClientOperations.h} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 ${GENERATOR} - do not modify. + * Supported AMQP versions: +%{VLIST} * ${major}-${minor} + */ + +#ifndef qpid_framing_AMQP_ClientOperations__ +#define qpid_framing_AMQP_ClientOperations__ + +#include <sstream> + +#include <FieldTable.h> +#include <ProtocolVersion.h> +#include <ProtocolVersionException.h> + +namespace qpid { +namespace framing { + +class AMQP_ClientProxy; + +class AMQP_ClientOperations +{ +protected: + ProtocolVersion version; + AMQP_ClientOperations() {} + +public: + AMQP_ClientOperations(u_int8_t major, u_int8_t minor) : version(major, minor) {} + AMQP_ClientOperations(ProtocolVersion& version) : version(version) {} + virtual ~AMQP_ClientOperations() {} + + inline u_int8_t getMajor() const { return version.getMajor(); } + inline u_int8_t getMinor() const { return version.getMinor(); } + inline const ProtocolVersion& getVersion() const { return version; } + inline bool isVersion(u_int8_t _major, u_int8_t _minor) const + { + return version.equals(_major, _minor); + } + inline bool isVersion(ProtocolVersion& _version) const + { + return version.equals(_version); + } + + // Include framing constant declarations + #include <AMQP_Constants.h> + + // Inner classes + +%{CLIST} ${coh_inner_class} + + // Method handler get methods + +%{CLIST} ${coh_method_handler_get_method} + +}; /* class AMQP_ClientOperations */ + +} /* namespace framing */ +} /* namespace qpid */ + +#endif diff --git a/qpid/gentools/templ.cpp/model/AMQP_ClientProxy.cpp.tmpl b/qpid/gentools/templ.cpp/model/AMQP_ClientProxy.cpp.tmpl new file mode 100644 index 0000000000..8cca6e5cec --- /dev/null +++ b/qpid/gentools/templ.cpp/model/AMQP_ClientProxy.cpp.tmpl @@ -0,0 +1,52 @@ +&{AMQP_ClientProxy.cpp} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 ${GENERATOR} - do not modify. + * Supported AMQP versions: +%{VLIST} * ${major}-${minor} + */ + +#include <sstream> + +#include <AMQP_ClientProxy.h> +#include <AMQFrame.h> +%{MLIST} ${cpc_method_body_include} + +namespace qpid { +namespace framing { + +AMQP_ClientProxy::AMQP_ClientProxy(OutputHandler* out, u_int8_t major, u_int8_t minor) : +%{CLIST} ${cpc_constructor_initializer} + +{} + + // Inner class instance get methods + +%{CLIST} ${cpc_inner_class_get_method} + + // Inner class implementation + +%{CLIST} ${cpc_inner_class_impl} + +} /* namespace framing */ +} /* namespace qpid */ diff --git a/qpid/gentools/templ.cpp/model/AMQP_ClientProxy.h.tmpl b/qpid/gentools/templ.cpp/model/AMQP_ClientProxy.h.tmpl new file mode 100644 index 0000000000..0653ed7186 --- /dev/null +++ b/qpid/gentools/templ.cpp/model/AMQP_ClientProxy.h.tmpl @@ -0,0 +1,75 @@ +&{AMQP_ClientProxy.h} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 ${GENERATOR} - do not modify. + * Supported AMQP versions: +%{VLIST} * ${major}-${minor} + */ + +#ifndef qpid_framing_AMQP_ClientProxy__ +#define qpid_framing_AMQP_ClientProxy__ + +#include <AMQP_ClientOperations.h> +#include <FieldTable.h> +#include <OutputHandler.h> + +namespace qpid { +namespace framing { + +class AMQP_ClientProxy : public AMQP_ClientOperations +{ +private: + + ProtocolVersion version; + OutputHandler* out; +%{CLIST} ${cph_handler_pointer_defn} + +public: + AMQP_ClientProxy(OutputHandler* out, u_int8_t major, u_int8_t minor); + ProtocolVersion& getProtocolVersion() {return version;} + virtual ~AMQP_ClientProxy() {} + + // Get methods for handlers + +%{CLIST} ${cph_handler_pointer_get_method} + + // Inner class definitions + +%{CLIST} ${cph_inner_class_defn} + +private: + // Inner class instances + +%{CLIST} ${cph_inner_class_instance} + +public: + // Inner class instance get methods + +%{CLIST} ${cph_inner_class_get_method} + +}; /* class AMQP_ClientProxy */ + +} /* namespace framing */ +} /* namespace qpid */ + +#endif diff --git a/qpid/gentools/templ.cpp/model/AMQP_Constants.h.tmpl b/qpid/gentools/templ.cpp/model/AMQP_Constants.h.tmpl new file mode 100644 index 0000000000..4631bc8de6 --- /dev/null +++ b/qpid/gentools/templ.cpp/model/AMQP_Constants.h.tmpl @@ -0,0 +1,34 @@ +&{AMQP_Constants.h} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 ${GENERATOR} - do not modify. + * Supported AMQP versions: +%{VLIST} * ${major}-${minor} + */ + // NOTE: This file is intended to be included within the class structure of both + // the client and server operations classes. These need to have <sstream> included. + + // Constant getValue methods + +%{TLIST} ${ch_get_value_method} +
\ No newline at end of file diff --git a/qpid/gentools/templ.cpp/model/AMQP_HighestVersion.h.tmpl b/qpid/gentools/templ.cpp/model/AMQP_HighestVersion.h.tmpl new file mode 100644 index 0000000000..9753b454ba --- /dev/null +++ b/qpid/gentools/templ.cpp/model/AMQP_HighestVersion.h.tmpl @@ -0,0 +1,42 @@ +&{AMQP_HighestVersion.h} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 ${GENERATOR} - do not modify. + * Supported AMQP versions: +%{VLIST} * ${major}-${minor} + */ +#ifndef qpid_framing_highestProtocolVersion__ +#define qpid_framing_highestProtocolVersion__ + +#include <ProtocolVersion.h> + + +namespace qpid { +namespace framing { + +static ProtocolVersion highestProtocolVersion(${hv_latest_major}, ${hv_latest_minor}); + +} /* namespace framing */ +} /* namespace qpid */ + +#endif diff --git a/qpid/gentools/templ.cpp/model/AMQP_MethodVersionMap.cpp.tmpl b/qpid/gentools/templ.cpp/model/AMQP_MethodVersionMap.cpp.tmpl new file mode 100644 index 0000000000..dc2a890c88 --- /dev/null +++ b/qpid/gentools/templ.cpp/model/AMQP_MethodVersionMap.cpp.tmpl @@ -0,0 +1,62 @@ +&{AMQP_MethodVersionMap.cpp} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 ${GENERATOR} - do not modify. + * Supported AMQP versions: +%{VLIST} * ${major}-${minor} + */ + +#include <sstream> + +#include <AMQP_MethodVersionMap.h> + +namespace qpid +{ +namespace framing +{ + +AMQP_MethodVersionMap::AMQP_MethodVersionMap() +{ +%{CLIST} ${mc_create_method_body_map_entry} +} + +AMQMethodBody* AMQP_MethodVersionMap::createMethodBody(u_int16_t classId, u_int16_t methodId, u_int8_t major, u_int8_t minor) +{ + iterator itr = find(createMapKey(classId, methodId, major, minor)); + if (itr == end()) + { + std::stringstream ss; + ss << "Unable to find MethodBody class for classId = " << classId << ", methodId = " << + methodId << ", AMQ protocol version = " << major << "-" << minor << "."; + throw ProtocolVersionException(ss.str()); + } + return (itr->second)(major, minor); +} + +u_int64_t AMQP_MethodVersionMap::createMapKey(u_int16_t classId, u_int16_t methodId, u_int8_t major, u_int8_t minor) +{ + return ((u_int64_t)classId<<48) + ((u_int64_t)methodId<<32) + ((u_int64_t)major<<16) + minor; +} + +} /* namespace framing */ +} /* namespace qpid */ diff --git a/qpid/gentools/templ.cpp/model/AMQP_MethodVersionMap.h.tmpl b/qpid/gentools/templ.cpp/model/AMQP_MethodVersionMap.h.tmpl new file mode 100644 index 0000000000..c197871d4b --- /dev/null +++ b/qpid/gentools/templ.cpp/model/AMQP_MethodVersionMap.h.tmpl @@ -0,0 +1,57 @@ +&{AMQP_MethodVersionMap.h} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 ${GENERATOR} - do not modify. + * Supported AMQP versions: +%{VLIST} * ${major}-${minor} + */ + +#ifndef qpid_framing_AMQP_MethodVersionMap__ +#define qpid_framing_AMQP_MethodVersionMap__ + +#include <map> +#include <AMQMethodBody.h> + +%{MLIST} ${mc_method_body_include} + +namespace qpid +{ +namespace framing +{ + +template <class T> AMQMethodBody* createMethodBodyFn(u_int8_t major, u_int8_t minor) { return new T(major, minor); } +typedef AMQMethodBody* (*fnPtr)(u_int8_t, u_int8_t); + +class AMQP_MethodVersionMap: public std::map<u_int64_t, fnPtr> +{ +protected: + u_int64_t createMapKey(u_int16_t classId, u_int16_t methodId, u_int8_t major, u_int8_t minor); +public: + AMQP_MethodVersionMap(); + AMQMethodBody* createMethodBody(u_int16_t classId, u_int16_t methodId, u_int8_t major, u_int8_t minor); +}; + +} /* namespace framing */ +} /* namespace qpid */ + +#endif diff --git a/qpid/gentools/templ.cpp/model/AMQP_ServerOperations.h.tmpl b/qpid/gentools/templ.cpp/model/AMQP_ServerOperations.h.tmpl new file mode 100644 index 0000000000..e87723667b --- /dev/null +++ b/qpid/gentools/templ.cpp/model/AMQP_ServerOperations.h.tmpl @@ -0,0 +1,83 @@ +&{AMQP_ServerOperations.h} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 ${GENERATOR} - do not modify. + * Supported AMQP versions: +%{VLIST} * ${major}-${minor} + */ + +#ifndef qpid_framing_AMQP_ServerOperations__ +#define qpid_framing_AMQP_ServerOperations__ + +#include <sstream> + +#include <FieldTable.h> +#include <ProtocolVersion.h> +#include <ProtocolVersionException.h> + +namespace qpid { +namespace framing { + +class AMQP_ServerProxy; +class AMQP_ClientProxy; + +class AMQP_ServerOperations +{ +protected: + ProtocolVersion version; + AMQP_ServerOperations() {} + +public: + AMQP_ServerOperations(u_int8_t major, u_int8_t minor) : version(major, minor) {} + AMQP_ServerOperations(ProtocolVersion& version) : version(version) {} + virtual ~AMQP_ServerOperations() {} + + inline u_int8_t getMajor() const { return version.getMajor(); } + inline u_int8_t getMinor() const { return version.getMinor(); } + inline const ProtocolVersion& getVersion() const { return version; } + inline bool isVersion(u_int8_t _major, u_int8_t _minor) const + { + return version.equals(_major, _minor); + } + inline bool isVersion(ProtocolVersion& _version) const + { + return version.equals(_version); + } + + // Include framing constant declarations + #include <AMQP_Constants.h> + + // Inner classes + +%{CLIST} ${soh_inner_class} + + // Method handler get methods + +%{CLIST} ${soh_method_handler_get_method} + +}; /* class AMQP_ServerOperations */ + +} /* namespace framing */ +} /* namespace qpid */ + +#endif diff --git a/qpid/gentools/templ.cpp/model/AMQP_ServerProxy.cpp.tmpl b/qpid/gentools/templ.cpp/model/AMQP_ServerProxy.cpp.tmpl new file mode 100644 index 0000000000..cce369f98b --- /dev/null +++ b/qpid/gentools/templ.cpp/model/AMQP_ServerProxy.cpp.tmpl @@ -0,0 +1,51 @@ +&{AMQP_ServerProxy.cpp} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 ${GENERATOR} - do not modify. + * Supported AMQP versions: +%{VLIST} * ${major}-${minor} + */ + +#include <sstream> + +#include <AMQP_ServerProxy.h> +#include <AMQFrame.h> +%{MLIST} ${spc_method_body_include} + +namespace qpid { +namespace framing { + +AMQP_ServerProxy::AMQP_ServerProxy(OutputHandler* out, u_int8_t major, u_int8_t minor) : +%{CLIST} ${spc_constructor_initializer} +{} + + // Inner class instance get methods + +%{CLIST} ${spc_inner_class_get_method} + + // Inner class implementation + +%{CLIST} ${spc_inner_class_impl} + +} /* namespace framing */ +} /* namespace qpid */ diff --git a/qpid/gentools/templ.cpp/model/AMQP_ServerProxy.h.tmpl b/qpid/gentools/templ.cpp/model/AMQP_ServerProxy.h.tmpl new file mode 100644 index 0000000000..fab29f2c60 --- /dev/null +++ b/qpid/gentools/templ.cpp/model/AMQP_ServerProxy.h.tmpl @@ -0,0 +1,74 @@ +&{AMQP_ServerProxy.h} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 ${GENERATOR} - do not modify. + * Supported AMQP versions: +%{VLIST} * ${major}-${minor} + */ + +#ifndef qpid_framing_AMQP_ServerProxy__ +#define qpid_framing_AMQP_ServerProxy__ + +#include <AMQP_ServerOperations.h> +#include <FieldTable.h> +#include <OutputHandler.h> + +namespace qpid { +namespace framing { + +class AMQP_ServerProxy : public AMQP_ServerOperations +{ +private: + ProtocolVersion version; + OutputHandler* out; +%{CLIST} ${sph_handler_pointer_defn} + +public: + AMQP_ServerProxy(OutputHandler* out, u_int8_t major, u_int8_t minor); + ProtocolVersion& getProtocolVersion() {return version;} + virtual ~AMQP_ServerProxy() {} + + // Get methods for handlers + +%{CLIST} ${sph_handler_pointer_get_method} + + // Inner class definitions + +%{CLIST} ${sph_inner_class_defn} + +private: + // Inner class instances + +%{CLIST} ${sph_inner_class_instance} + +public: + // Inner class instance get methods + +%{CLIST} ${sph_inner_class_get_method} + +}; /* class AMQP_ServerProxy */ + +} /* namespace framing */ +} /* namespace qpid */ + +#endif diff --git a/qpid/gentools/templ.java/PropertyContentHeaderClass.tmpl b/qpid/gentools/templ.java/PropertyContentHeaderClass.tmpl new file mode 100644 index 0000000000..ab6406b1fe --- /dev/null +++ b/qpid/gentools/templ.java/PropertyContentHeaderClass.tmpl @@ -0,0 +1,208 @@ +&{${CLASS}ContentHeaderProperties.java} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 ${GENERATOR} - do not modify. + * Supported AMQP versions: +%{VLIST} * ${major}-${minor} + */ + +package org.apache.qpid.framing; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.mina.common.ByteBuffer; + +public class ${CLASS}ContentHeaderProperties implements ContentHeaderProperties +{ + private static final Logger logger = LoggerFactory.getLogger(BasicContentHeaderProperties.class); + + /** + * We store the encoded form when we decode the content header so that if we need to + * write it out without modifying it we can do so without incurring the expense of + * reencoding it. + */ + private byte[] encodedBuffer; + + /** + * Flag indicating whether the entire content header has been decoded yet. + */ + private boolean decodedFlag = true; + + /** + * We have some optimisations for partial decoding for maximum performance. The + * headers are used in the broker for routing in some cases so we can decode that + * separately. + */ + private boolean decodedHeadersFlag = true; + + /** + * We have some optimisations for partial decoding for maximum performance. The + * content type is used by all clients to determine the message type. + */ + private boolean decodedContentTypeFlag = true; + + /** + * AMQP major and minor version of this instance. + */ + private byte major; + private byte minor; + + /** + * Property flags. + */ + ${pch_property_flags_declare} + + // Header fields from specification +%{FLIST} ${field_declaration} + + /** + * Constructor + */ + public ${CLASS}ContentHeaderProperties(byte major, byte minor) + { + this.major = major; + this.minor = minor; + + // Although one flag is initialized per property, the flags are used + // in ordinal order of the AMQP version represented by this instance, + // thus the number of flags actually used may be less than the total + // number defined. + ${pch_property_flags_initializer} + } + + public int getPropertyListSize() + { + if (encodedBuffer != null) + { + return encodedBuffer.length; + } + else + { + int size = 0; +%{FLIST} ${pch_field_list_size} + return size; + } + } + + private void clearEncodedForm() + { + if (!decodedFlag && encodedBuffer != null) + { + //decode(); + } + encodedBuffer = null; + } + + public void setPropertyFlags(int[] compactPropertyFlags) + throws AMQProtocolVersionException + { + clearEncodedForm(); +${pch_compact_property_flags_check} +%{FLIST} ${pch_set_compact_property_flags} + } + + public int[] getPropertyFlags() + { + int[] compactPropertyFlags = new int[] { 0 }; +${pch_compact_property_flags_initializer} +%{FLIST} ${pch_get_compact_property_flags} + return compactPropertyFlags; + } + + public void writePropertyListPayload(ByteBuffer buffer) + { + if (encodedBuffer != null) + { + buffer.put(encodedBuffer); + } + else + { +%{FLIST} ${pch_field_list_payload} + } + } + + public void populatePropertiesFromBuffer(ByteBuffer buffer, int[] propertyFlags, int size) + throws AMQFrameDecodingException, AMQProtocolVersionException + { + setPropertyFlags(propertyFlags); + + if (logger.isDebugEnabled()) + { + logger.debug("Property flags: " + propertyFlags); + } + decode(buffer); + /*encodedBuffer = new byte[size]; + buffer.get(encodedBuffer, 0, size); + decodedFlag = false; + decodedHeadersFlag = false; + decodedContentTypeFlag = false;*/ + } + + private void decode(ByteBuffer buffer) + { + //ByteBuffer buffer = ByteBuffer.wrap(encodedBuffer); + int pos = buffer.position(); + try + { +%{FLIST} ${pch_field_list_decode} + // This line does nothing, but prevents a compiler error (Exception not thrown) + // if this block is empty. + if (false) throw new AMQFrameDecodingException(""); + } + catch (AMQFrameDecodingException e) + { + throw new RuntimeException("Error in content header data: " + e); + } + + final int endPos = buffer.position(); + buffer.position(pos); + final int len = endPos - pos; + encodedBuffer = new byte[len]; + final int limit = buffer.limit(); + buffer.limit(endPos); + buffer.get(encodedBuffer, 0, len); + buffer.limit(limit); + buffer.position(endPos); + decodedFlag = true; + } + + private void decodeIfNecessary() + { + if (!decodedFlag) + { + //decode(); + } + } + + // Field clear methods + +%{FLIST} ${pch_field_clear_methods} + + // Field get methods + +%{FLIST} ${pch_field_get_methods} + + // Field set methods + +%{FLIST} ${pch_field_set_methods} +} diff --git a/qpid/gentools/templ.java/method/version/MethodBodyClass.vm b/qpid/gentools/templ.java/method/version/MethodBodyClass.vm new file mode 100644 index 0000000000..bb62438a65 --- /dev/null +++ b/qpid/gentools/templ.java/method/version/MethodBodyClass.vm @@ -0,0 +1,190 @@ +#macro( UpperCamel $name ) +#set( $name = "${name.substring(0,1).toUpperCase()}${name.substring(1)}" ) +#end +#macro( toUpperCamel $name )${name.substring(0,1).toUpperCase()}${name.substring(1)}#end + + + +#set( $amqp_ClassName = $amqpClass.Name) +#UpperCamel( $amqp_ClassName ) +#set( $amqp_MethodName = $amqpMethod.Name ) +#UpperCamel( $amqp_MethodName ) +#set( $javaClassName = "${amqp_ClassName}${amqp_MethodName}BodyImpl" ) +#set( $interfaceName = "${amqp_ClassName}${amqp_MethodName}Body" ) +#set( $amqpPackageName = "amqp_$version.getMajor()_$version.getMinor()" ) + +#set( $filename = "${amqpPackageName}/${javaClassName}.java") +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 ${generator} - do not modify. + * Supported AMQP version: + * $version.getMajor()-$version.getMinor() + */ + +#set( $clazz = $amqpClass.asSingleVersionClass( $version ) ) +#set( $method = $amqpMethod.asSingleVersionMethod( $version ) ) + +package org.apache.qpid.framing.amqp_$version.getMajor()_$version.getMinor(); + +import java.util.HashMap; + +import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.framing.*; + +public class ${javaClassName} extends AMQMethodBody_$version.getMajor()_$version.getMinor() implements $interfaceName +{ + private static final AMQMethodBodyInstanceFactory FACTORY_INSTANCE = new AMQMethodBodyInstanceFactory() + { + public AMQMethodBody newInstance(ByteBuffer in, long size) throws AMQFrameDecodingException + { + return new ${javaClassName}(in); + } + + + }; + + + public static AMQMethodBodyInstanceFactory getFactory() + { + return FACTORY_INSTANCE; + } + + public static int CLASS_ID = $clazz.ClassId; + + public static int METHOD_ID = $method.MethodId; + + + + // Fields declared in specification +#foreach( $field in $method.ConsolidatedFields ) + private final $field.NativeType _$field.getName(); // $field.UnderlyingFields +#end + + + // Constructor + + public ${javaClassName}(ByteBuffer buffer) throws AMQFrameDecodingException + { +#foreach( $field in $method.ConsolidatedFields ) + _$field.Name = read$field.getEncodingType()( buffer ); +#end + } + + public ${javaClassName}( +#foreach( $field in $method.FieldList ) +#if( $velocityCount == $method.getFieldList().size() ) + $field.NativeType $field.Name +#else + $field.NativeType $field.Name, +#end +#end) + { +#set( $consolidatedFieldName = "" ) +#foreach( $field in $method.FieldList ) +#if( $method.isConsolidated( $field.Name ) ) +#if( !$method.getConsolidatedFieldName( $field.Name ).equals( $consolidatedFieldName ) ) +#if( !$consolidatedFieldName.equals("") ) + _$consolidatedFieldName = $consolidatedFieldName; // 1 +#end +#set( $consolidatedFieldName = $method.getConsolidatedFieldName( $field.Name ) ) + byte $consolidatedFieldName = (byte)0; +#end + if( $field.Name ) + { + $consolidatedFieldName = (byte) (((int) $consolidatedFieldName) | (1 << $method.getPositionInBitField( $field.Name ))); + } +#if( $velocityCount == $method.getFieldList().size()) + _$consolidatedFieldName = $consolidatedFieldName; +#else + +#end +#else +#if( !$consolidatedFieldName.equals("") ) + _$consolidatedFieldName = $consolidatedFieldName; +#end +#set( $consolidatedFieldName = "" ) + _$field.Name = $field.Name; +#end +#end + } + + public int getClazz() + { + return CLASS_ID; + } + + public int getMethod() + { + return METHOD_ID; + } + + +#foreach( $field in $method.FieldList ) + public final $field.NativeType get#toUpperCamel( ${field.Name} )() + { +#if( $method.isConsolidated( $field.Name ) ) + return (((int)(_$method.getConsolidatedFieldName( $field.Name ))) & ( 1 << $method.getPositionInBitField( $field.Name ))) != 0; +#else + return _$field.Name; +#end + } +#end + + protected int getBodySize() + { + int size = 0; +#foreach( $field in $method.ConsolidatedFields ) +#if( $field.isFixedSize() ) + size += $field.Size; +#else + size += getSizeOf( _$field.Name ); +#end +#end + return size; + } + + public void writeMethodPayload(ByteBuffer buffer) + { +#foreach( $field in $method.ConsolidatedFields ) + + write$field.getEncodingType()( buffer, _$field.Name ); +#end + } + + + public String toString() + { + StringBuffer buf = new StringBuffer("[$javaClassName: "); +#foreach( $field in $method.FieldList ) + buf.append( "$field.Name=" ); + buf.append( get#toUpperCamel( $field.Name )() ); +#if( $velocityCount != $method.FieldList.size() ) + buf.append( ", " ); +#end +#end + buf.append("]"); + return buf.toString(); + } + + +} diff --git a/qpid/gentools/templ.java/model/ProtocolVersionListClass.vm b/qpid/gentools/templ.java/model/ProtocolVersionListClass.vm new file mode 100644 index 0000000000..bcf7db345b --- /dev/null +++ b/qpid/gentools/templ.java/model/ProtocolVersionListClass.vm @@ -0,0 +1,154 @@ +#set( $filename = "ProtocolVersion.java" ) +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT 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 $generator - do not modify. +* Supported AMQP versions: +#foreach( $version in $model.getVersionSet() ) +* $version.getMajor()-$version.getMinor() +#end +*/ + +package org.apache.qpid.framing; + +import java.util.SortedSet; +import java.util.Collections; +import java.util.TreeSet; + + +public class ProtocolVersion implements Comparable +{ + private final byte _majorVersion; + private final byte _minorVersion; + private final String _stringFormat; + + + public ProtocolVersion(byte majorVersion, byte minorVersion) + { + _majorVersion = majorVersion; + _minorVersion = minorVersion; + _stringFormat = _majorVersion+"-"+_minorVersion; + } + + public byte getMajorVersion() + { + return _majorVersion; + } + + public byte getMinorVersion() + { + return _minorVersion; + } + + public String toString() + { + return _stringFormat; + } + + public int compareTo(Object o) + { + ProtocolVersion pv = (ProtocolVersion) o; + + /* + * 0-8 has it's major and minor numbers the wrong way round (it's actually 8-0)... + * so we need to deal with that case specially + */ + + if((_majorVersion == (byte) 8) && (_minorVersion == (byte) 0)) + { + ProtocolVersion fixedThis = new ProtocolVersion(_minorVersion, _majorVersion); + return fixedThis.compareTo(pv); + } + + if((pv.getMajorVersion() == (byte) 8) && (pv.getMinorVersion() == (byte) 0)) + { + ProtocolVersion fixedOther = new ProtocolVersion(pv.getMinorVersion(), pv.getMajorVersion()); + return this.compareTo(fixedOther); + } + + if(_majorVersion > pv.getMajorVersion()) + { + return 1; + } + else if(_majorVersion < pv.getMajorVersion()) + { + return -1; + } + else if(_minorVersion > pv.getMinorVersion()) + { + return 1; + } + else if(getMinorVersion() < pv.getMinorVersion()) + { + return -1; + } + else + { + return 0; + } + + } + + public boolean equals(Object o) + { + return o != null && (o == this || (compareTo(o) == 0)); + } + + public int hashCode() + { + return (0xFF & (int)_minorVersion) | ((0xFF & (int)_majorVersion) << 8); + } + + + public boolean isSupported() + { + return _supportedVersions.contains(this); + } + + public static ProtocolVersion getLatestSupportedVersion() + { + return _supportedVersions.last(); + } + + private static final SortedSet<ProtocolVersion> _supportedVersions; + + static + { + SortedSet<ProtocolVersion> versions = new TreeSet<ProtocolVersion>(); + +#foreach( $version in $model.getVersionSet() ) + versions.add(new ProtocolVersion((byte)$version.getMajor(),(byte)$version.getMinor())); +#end + _supportedVersions = Collections.unmodifiableSortedSet(versions); + } + + + public static SortedSet<ProtocolVersion> getSupportedProtocolVersions() + { + return _supportedVersions; + } + + + + + +} diff --git a/qpid/gentools/templ.java/model/version/AmqpConstantsClass.vm b/qpid/gentools/templ.java/model/version/AmqpConstantsClass.vm new file mode 100644 index 0000000000..8d459f2977 --- /dev/null +++ b/qpid/gentools/templ.java/model/version/AmqpConstantsClass.vm @@ -0,0 +1,37 @@ +&{AmqpConstants.java} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 ${GENERATOR} - do not modify. + * Supported AMQP versions: +%{VLIST} * ${major}-${minor} + */ + +package org.apache.qpid.framing; + +class AmqpConstants +{ + // Constant getValue methods + +%{TLIST} ${const_get_method} + +} diff --git a/qpid/gentools/templ.java/model/version/MethodRegistryClass.vm b/qpid/gentools/templ.java/model/version/MethodRegistryClass.vm new file mode 100644 index 0000000000..82287e7f8f --- /dev/null +++ b/qpid/gentools/templ.java/model/version/MethodRegistryClass.vm @@ -0,0 +1,145 @@ +#set( $filename = "amqp_$version.getMajor()_$version.getMinor()/MethodRegistry_${version.getMajor()}_${version.getMinor()}.java") +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 $generator - do not modify. + * Supported AMQP version: + * $version.getMajor()-$version.getMinor() + */ + +package org.apache.qpid.framing.amqp_${version.getMajor()}_${version.getMinor()}; + +import org.apache.qpid.framing.AMQMethodBodyInstanceFactory; +import org.apache.qpid.framing.AMQFrameDecodingException; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.ProtocolVersion; + + +import org.apache.log4j.Logger; +import org.apache.mina.common.ByteBuffer; + +public class MethodRegistry_$version.getMajor()_$version.getMinor() extends MethodRegistry +{ + + private static final Logger _log = Logger.getLogger(MethodRegistry.class); + +#set( $specificModel = $model.asSingleVersionModel() ) + + + + private final AMQMethodBodyInstanceFactory[][] _factories = new AMQMethodBodyInstanceFactory[$specificModel.getMaximumClassId()+1][]; + + public MethodRegistry_$version.getMajor()_$version.getMinor()() + { + this(new ProtocolVersion((byte)$version.getMajor(),(byte)$version.getMinor())); + } + + public MethodRegistry_$version.getMajor()_$version.getMinor()(ProtocolVersion pv) + { + super(pv); +#foreach( $amqpClass in $specificModel.getClassList() ) +#set( $amqpClassNameFirstChar = $amqpClass.getName().substring(0,1) ) +#set( $amqpClassNameFirstCharU = $amqpClassNameFirstChar.toUpperCase() ) +#set( $amqpClassNameUpperCamel = "$amqpClassNameFirstCharU$amqpClass.getName().substring(1)" ) + + + + // Register method body instance factories for the $amqpClassNameUpperCamel class. + + _factories[$amqpClass.getClassId()] = new AMQMethodBodyInstanceFactory[$amqpClass.getMaximumMethodId()+1]; + +#foreach( $amqpMethod in $amqpClass.getMethodList() ) +#set( $amqpMethodNameFirstChar = $amqpMethod.getName().substring(0,1) ) +#set( $amqpMethodNameFirstCharU = $amqpMethodNameFirstChar.toUpperCase() ) +#set( $amqpMethodNameUpperCamel = "$amqpMethodNameFirstCharU$amqpMethod.getName().substring(1)" ) + _factories[$amqpClass.getClassId()][$amqpMethod.getMethodId()] = ${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}BodyImpl.getFactory(); +#end + +#end + + + } + + + public AMQMethodBody convertToBody(ByteBuffer in, long size) + throws AMQFrameDecodingException + { + int classId = in.getUnsignedShort(); + int methodId = in.getUnsignedShort(); + + AMQMethodBodyInstanceFactory bodyFactory; + try + { + bodyFactory = _factories[classId][methodId]; + } + catch(NullPointerException e) + { + throw new AMQFrameDecodingException(_log, + "Class " + classId + " unknown in AMQP version $version.getMajor()-$version.getMinor()" + + " (while trying to decode class " + classId + " method " + methodId + "."); + } + catch(IndexOutOfBoundsException e) + { + if(classId >= _factories.length) + { + throw new AMQFrameDecodingException(_log, + "Class " + classId + " unknown in AMQP version $version.getMajor()-$version.getMinor()" + + " (while trying to decode class " + classId + " method " + methodId + "."); + + } + else + { + throw new AMQFrameDecodingException(_log, + "Method " + methodId + " unknown in AMQP version $version.getMajor()-$version.getMinor()" + + " (while trying to decode class " + classId + " method " + methodId + "."); + + } + } + + + if (bodyFactory == null) + { + throw new AMQFrameDecodingException(_log, + "Method " + methodId + " unknown in AMQP version $version.getMajor()-$version.getMinor()" + + " (while trying to decode class " + classId + " method " + methodId + "."); + } + + + return bodyFactory.newInstance(in, size); + + + } + + + public int getMaxClassId() + { + return $specificModel.getMaximumClassId(); + } + + public int getMaxMethodId(int classId) + { + return _factories[classId].length - 1; + } + + +} diff --git a/qpid/gentools/xml-src/amqp-0.10.test.xml b/qpid/gentools/xml-src/amqp-0.10.test.xml new file mode 100644 index 0000000000..5d3d80648b --- /dev/null +++ b/qpid/gentools/xml-src/amqp-0.10.test.xml @@ -0,0 +1,4241 @@ +<?xml version = "1.0"?> + +<!-- + EDITORS: (PH) Pieter Hintjens <ph@imatix.com> + (KvdR) Kim van der Riet <kim.vdriet@redhat.com> + + These editors have been assigned by the AMQP working group. + Please do not edit/commit this file without consulting with + one of the above editors. + ======================================================== + + TODOs + - see TODO comments in the text +--> + +<!-- + Copyright Notice + ================ + (c) Copyright JPMorgan Chase Bank & Co., Cisco Systems, Inc., Envoy Technologies Inc., + iMatix Corporation, IONA\ufffd Technologies, Red Hat, Inc., + TWIST Process Innovations, and 29West Inc. 2006. All rights reserved. + + License + ======= + JPMorgan Chase Bank & Co., Cisco Systems, Inc., Envoy Technologies Inc., iMatix + Corporation, IONA\ufffd Technologies, Red Hat, Inc., TWIST Process Innovations, and + 29West Inc. (collectively, the "Authors") each hereby grants to you a worldwide, + perpetual, royalty-free, nontransferable, nonexclusive license to + (i) copy, display, and implement the Advanced Messaging Queue Protocol + ("AMQP") Specification and (ii) the Licensed Claims that are held by + the Authors, all for the purpose of implementing the Advanced Messaging + Queue Protocol Specification. Your license and any rights under this + Agreement will terminate immediately without notice from + any Author if you bring any claim, suit, demand, or action related to + the Advanced Messaging Queue Protocol Specification against any Author. + Upon termination, you shall destroy all copies of the Advanced Messaging + Queue Protocol Specification in your possession or control. + + As used hereunder, "Licensed Claims" means those claims of a patent or + patent application, throughout the world, excluding design patents and + design registrations, owned or controlled, or that can be sublicensed + without fee and in compliance with the requirements of this + Agreement, by an Author or its affiliates now or at any + future time and which would necessarily be infringed by implementation + of the Advanced Messaging Queue Protocol Specification. A claim is + necessarily infringed hereunder only when it is not possible to avoid + infringing it because there is no plausible non-infringing alternative + for implementing the required portions of the Advanced Messaging Queue + Protocol Specification. Notwithstanding the foregoing, Licensed Claims + shall not include any claims other than as set forth above even if + contained in the same patent as Licensed Claims; or that read solely + on any implementations of any portion of the Advanced Messaging Queue + Protocol Specification that are not required by the Advanced Messaging + Queue Protocol Specification, or that, if licensed, would require a + payment of royalties by the licensor to unaffiliated third parties. + Moreover, Licensed Claims shall not include (i) any enabling technologies + that may be necessary to make or use any Licensed Product but are not + themselves expressly set forth in the Advanced Messaging Queue Protocol + Specification (e.g., semiconductor manufacturing technology, compiler + technology, object oriented technology, networking technology, operating + system technology, and the like); or (ii) the implementation of other + published standards developed elsewhere and merely referred to in the + body of the Advanced Messaging Queue Protocol Specification, or + (iii) any Licensed Product and any combinations thereof the purpose or + function of which is not required for compliance with the Advanced + Messaging Queue Protocol Specification. For purposes of this definition, + the Advanced Messaging Queue Protocol Specification shall be deemed to + include both architectural and interconnection requirements essential + for interoperability and may also include supporting source code artifacts + where such architectural, interconnection requirements and source code + artifacts are expressly identified as being required or documentation to + achieve compliance with the Advanced Messaging Queue Protocol Specification. + + As used hereunder, "Licensed Products" means only those specific portions + of products (hardware, software or combinations thereof) that implement + and are compliant with all relevant portions of the Advanced Messaging + Queue Protocol Specification. + + The following disclaimers, which you hereby also acknowledge as to any + use you may make of the Advanced Messaging Queue Protocol Specification: + + THE ADVANCED MESSAGING QUEUE PROTOCOL SPECIFICATION IS PROVIDED "AS IS," + AND THE AUTHORS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR + IMPLIED, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, OR TITLE; THAT THE + CONTENTS OF THE ADVANCED MESSAGING QUEUE PROTOCOL SPECIFICATION ARE + SUITABLE FOR ANY PURPOSE; NOR THAT THE IMPLEMENTATION OF THE ADVANCED + MESSAGING QUEUE PROTOCOL SPECIFICATION WILL NOT INFRINGE ANY THIRD PARTY + PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. + + THE AUTHORS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL, + INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATING TO ANY + USE, IMPLEMENTATION OR DISTRIBUTION OF THE ADVANCED MESSAGING QUEUE + PROTOCOL SPECIFICATION. + + The name and trademarks of the Authors may NOT be used in any manner, + including advertising or publicity pertaining to the Advanced Messaging + Queue Protocol Specification or its contents without specific, written + prior permission. Title to copyright in the Advanced Messaging Queue + Protocol Specification will at all times remain with the Authors. + + No other rights are granted by implication, estoppel or otherwise. + + Upon termination of your license or rights under this Agreement, you + shall destroy all copies of the Advanced Messaging Queue Protocol + Specification in your possession or control. + + Trademarks + ========== + "JPMorgan", "JPMorgan Chase", "Chase", the JPMorgan Chase logo and the + Octagon Symbol are trademarks of JPMorgan Chase & Co. + + IMATIX and the iMatix logo are trademarks of iMatix Corporation sprl. + + IONA, IONA Technologies, and the IONA logos are trademarks of IONA + Technologies PLC and/or its subsidiaries. + + LINUX is a trademark of Linus Torvalds. RED HAT and JBOSS are registered + trademarks of Red Hat, Inc. in the US and other countries. + + Java, all Java-based trademarks and OpenOffice.org are trademarks of + Sun Microsystems, Inc. in the United States, other countries, or both. + + Other company, product, or service names may be trademarks or service + marks of others. + + Links to full AMQP specification: + ================================= + http://www.envoytech.org/spec/amq/ + http://www.iona.com/opensource/amqp/ + http://www.redhat.com/solutions/specifications/amqp/ + http://www.twiststandards.org/tiki-index.php?page=AMQ + http://www.imatix.com/amqp +--> + +<!-- + <!DOCTYPE amqp SYSTEM "amqp.dtd"> +--> + +<!-- XML Notes + + We use entities to indicate repetition; attributes to indicate properties. + + We use the 'name' attribute as an identifier, usually within the context + of the surrounding entities. + + We use spaces to seperate words in names, so that we can print names in + their natural form depending on the context - underlines for source code, + hyphens for written text, etc. + + We do not enforce any particular validation mechanism but we support all + mechanisms. The protocol definition conforms to a formal grammar that is + published seperately in several technologies. + + --> + +<amqp major = "0" minor = "10" port = "5672" comment = "AMQ Protocol"> + <!-- + ====================================================== + == CONSTANTS + ====================================================== + --> + <!-- Frame types --> + <constant name = "frame-method" value = "1" /> + <constant name = "frame-header" value = "2" /> + <constant name = "frame-body" value = "3" /> + <constant name = "frame-oob-method" value = "4" /> + <constant name = "frame-oob-header" value = "5" /> + <constant name = "frame-oob-body" value = "6" /> + <constant name = "frame-trace" value = "7" /> + <constant name = "frame-heartbeat" value = "8" /> + + <!-- Protocol constants --> + <constant name = "frame-min-size" value = "4096" /> + <constant name = "frame-end" value = "206" /> + + <!-- Reply codes --> + <constant name = "reply-success" value = "200"> + <doc> + Indicates that the method completed successfully. This reply code is + reserved for future use - the current protocol design does not use positive + confirmation and reply codes are sent only in case of an error. + </doc> + </constant> + + <constant name = "not-delivered" value = "310" class = "soft-error"> + <doc> + The client asked for a specific message that is no longer available. + The message was delivered to another client, or was purged from the queue + for some other reason. + </doc> + </constant> + + <constant name = "content-too-large" value = "311" class = "soft-error"> + <doc> + The client attempted to transfer content larger than the server could accept + at the present time. The client may retry at a later time. + </doc> + </constant> + + <constant name = "connection-forced" value = "320" class = "hard-error"> + <doc> + An operator intervened to close the connection for some reason. The client + may retry at some later date. + </doc> + </constant> + + <constant name = "invalid-path" value = "402" class = "hard-error"> + <doc> + The client tried to work with an unknown virtual host. + </doc> + </constant> + + <constant name = "access-refused" value = "403" class = "soft-error"> + <doc> + The client attempted to work with a server entity to which it has no + access due to security settings. + </doc> + </constant> + + <constant name = "not-found" value = "404" class = "soft-error"> + <doc>The client attempted to work with a server entity that does not exist.</doc> + </constant> + + <constant name = "resource-locked" value = "405" class = "soft-error"> + <doc> + The client attempted to work with a server entity to which it has no + access because another client is working with it. + </doc> + </constant> + + <constant name = "precondition-failed" value = "406" class = "soft-error"> + <doc> + The client requested a method that was not allowed because some precondition + failed. + </doc> + </constant> + + <constant name = "frame-error" value = "501" class = "hard-error"> + <doc> + The client sent a malformed frame that the server could not decode. This + strongly implies a programming error in the client. + </doc> + </constant> + + <constant name = "syntax-error" value = "502" class = "hard-error"> + <doc> + The client sent a frame that contained illegal values for one or more + fields. This strongly implies a programming error in the client. + </doc> + </constant> + + <constant name = "command-invalid" value = "503" class = "hard-error"> + <doc> + The client sent an invalid sequence of frames, attempting to perform an + operation that was considered invalid by the server. This usually implies + a programming error in the client. + </doc> + </constant> + + <constant name = "channel-error" value = "504" class = "hard-error"> + <doc> + The client attempted to work with a channel that had not been correctly + opened. This most likely indicates a fault in the client layer. + </doc> + </constant> + + <constant name = "resource-error" value = "506" class = "hard-error"> + <doc> + The server could not complete the method because it lacked sufficient + resources. This may be due to the client creating too many of some type + of entity. + </doc> + </constant> + + <constant name = "not-allowed" value = "530" class = "hard-error"> + <doc> + The client tried to work with some entity in a manner that is prohibited + by the server, due to security settings or by some other criteria. + </doc> + </constant> + + <constant name = "not-implemented" value = "540" class = "hard-error"> + <doc> + The client tried to use functionality that is not implemented in the + server. + </doc> + </constant> + + <constant name = "internal-error" value = "545" class = "hard-error"> + <doc> + The server could not complete the method because of an internal error. + The server may require intervention by an operator in order to resume + normal operations. + </doc> + </constant> + + <constant name = "test-double" value = "3.141592654"/> + <constant name = "test-str1" value = "hello, world!"/> + <constant name = "test-str2" value = "1.2.3.4"/> + + <!-- + ====================================================== + == DOMAIN TYPES + ====================================================== + --> + + <domain name = "access-ticket" type = "short" label = "access ticket granted by server"> + <doc> + An access ticket granted by the server for a certain set of access rights + within a specific realm. Access tickets are valid within the channel where + they were created, and expire when the channel closes. + </doc> + <assert check = "ne" value = "0" /> + </domain> + + <domain name = "class-id" type = "short" /> + + <domain name = "consumer-tag" type = "shortstr" label = "consumer tag"> + <doc> + Identifier for the consumer, valid within the current connection. + </doc> + </domain> + + <domain name = "delivery-tag" type = "longlong" label = "server-assigned delivery tag"> + <doc> + The server-assigned and channel-specific delivery tag + </doc> + <rule name = "channel-local"> + <doc> + The delivery tag is valid only within the channel from which the message was + received. I.e. a client MUST NOT receive a message on one channel and then + acknowledge it on another. + </doc> + </rule> + <rule name = "non-zero"> + <doc> + The server MUST NOT use a zero value for delivery tags. Zero is reserved + for client use, meaning "all messages so far received". + </doc> + </rule> + </domain> + + <domain name = "exchange-name" type = "shortstr" label = "exchange name"> + <doc> + The exchange name is a client-selected string that identifies the exchange for publish + methods. Exchange names may consist of any mixture of digits, letters, and underscores. + Exchange names are scoped by the virtual host. + </doc> + <assert check = "length" value = "127" /> + </domain> + + <domain name = "known-hosts" type = "shortstr" label = "list of known hosts"> + <doc> + Specifies the list of equivalent or alternative hosts that the server knows about, + which will normally include the current server itself. Clients can cache this + information and use it when reconnecting to a server after a failure. This field + may be empty. + </doc> + </domain> + + <domain name = "method-id" type = "long" /> + + <domain name = "no-ack" type = "bit" label = "no acknowledgement needed"> + <doc> + If this field is set the server does not expect acknowledgments for + messages. That is, when a message is delivered to the client the server + automatically and silently acknowledges it on behalf of the client. This + functionality increases performance but at the cost of reliability. + Messages can get lost if a client dies before it can deliver them to the + application. + </doc> + </domain> + + <domain name = "no-local" type = "bit" label = "do not deliver own messages"> + <doc> + If the no-local field is set the server will not send messages to the client that + published them. + </doc> + </domain> + + <domain name = "path" type = "shortstr"> + <doc> + Must start with a slash "/" and continue with path names separated by slashes. A path + name consists of any combination of at least one of [A-Za-z0-9] plus zero or more of + [.-_+!=:]. + </doc> + + <assert check = "notnull" /> + <assert check = "syntax" rule = "path" /> + <assert check = "length" value = "127" /> + </domain> + + <domain name = "peer-properties" type = "table"> + <doc> + This string provides a set of peer properties, used for identification, debugging, and + general information. + </doc> + </domain> + + <domain name = "queue-name" type = "shortstr" label = "queue name"> + <doc> + The queue name identifies the queue within the vhost. Queue names may consist of any + mixture of digits, letters, and underscores. + </doc> + <assert check = "length" value = "127" /> + </domain> + + <domain name = "redelivered" type = "bit" label = "message is being redelivered"> + <doc> + This indicates that the message has been previously delivered to this or + another client. + </doc> + <rule name = "implementation"> + <doc> + The server SHOULD try to signal redelivered messages when it can. When + redelivering a message that was not successfully acknowledged, the server + SHOULD deliver it to the original client if possible. + </doc> + <doc type = "scenario"> + Create a shared queue and publish a message to the queue. Consume the + message using explicit acknowledgements, but do not acknowledge the + message. Close the connection, reconnect, and consume from the queue + again. The message should arrive with the redelivered flag set. + </doc> + </rule> + <rule name = "hinting"> + <doc> + The client MUST NOT rely on the redelivered field but should take it as a + hint that the message may already have been processed. A fully robust + client must be able to track duplicate received messages on non-transacted, + and locally-transacted channels. + </doc> + </rule> + </domain> + + <domain name = "reply-code" type = "short" label = "reply code from server"> + <doc> + The reply code. The AMQ reply codes are defined as constants at the start + of this formal specification. + </doc> + <assert check = "notnull" /> + </domain> + + <domain name = "reply-text" type = "shortstr" label = "localised reply text"> + <doc> + The localised reply text. This text can be logged as an aid to resolving + issues. + </doc> + <assert check = "notnull" /> + </domain> + + <!-- Elementary domains --> + <domain name = "bit" type = "bit" label = "single bit" /> + <domain name = "octet" type = "octet" label = "single octet" /> + <domain name = "short" type = "short" label = "16-bit integer" /> + <domain name = "long" type = "long" label = "32-bit integer" /> + <domain name = "longlong" type = "longlong" label = "64-bit integer" /> + <domain name = "shortstr" type = "shortstr" label = "short string" /> + <domain name = "longstr" type = "longstr" label = "long string" /> + <domain name = "timestamp" type = "timestamp" label = "64-bit timestamp" /> + <domain name = "table" type = "table" label = "field table" /> + + <!-- == CONNECTION ======================================================= --> + + <!-- TODO 0.81 - the 'handler' attribute of methods needs to be reviewed, and if + no current implementations use it, removed. /PH 2006/07/20 + --> + + <class name = "connection" handler = "connection" index = "10" label = "work with socket connections"> + <doc> + The connection class provides methods for a client to establish a network connection to + a server, and for both peers to operate the connection thereafter. + </doc> + + <doc type = "grammar"> + connection = open-connection *use-connection close-connection + open-connection = C:protocol-header + S:START C:START-OK + *challenge + S:TUNE C:TUNE-OK + C:OPEN S:OPEN-OK | S:REDIRECT + challenge = S:SECURE C:SECURE-OK + use-connection = *channel + close-connection = C:CLOSE S:CLOSE-OK + / S:CLOSE C:CLOSE-OK + </doc> + + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "start" synchronous = "1" index = "10" label = "start connection negotiation"> + <doc> + This method starts the connection negotiation process by telling the client the + protocol version that the server proposes, along with a list of security mechanisms + which the client can use for authentication. + </doc> + + <rule name = "protocol-name"> + <doc> + If the server cannot support the protocol specified in the protocol header, + it MUST close the socket connection without sending any response method. + </doc> + <doc type = "scenario"> + The client sends a protocol header containing an invalid protocol name. + The server must respond by closing the connection. + </doc> + </rule> + <rule name = "server-support"> + <doc> + The server MUST provide a protocol version that is lower than or equal to + that requested by the client in the protocol header. + </doc> + <doc type = "scenario"> + The client requests a protocol version that is higher than any valid + implementation, e.g. 9.0. The server must respond with a current + protocol version, e.g. 1.0. + </doc> + </rule> + <rule name = "client-support"> + <doc> + If the client cannot handle the protocol version suggested by the server + it MUST close the socket connection. + </doc> + <doc type = "scenario"> + The server sends a protocol version that is lower than any valid + implementation, e.g. 0.1. The client must respond by closing the + connection. + </doc> + </rule> + + <chassis name = "client" implement = "MUST" /> + <response name = "start-ok" /> + + <field name = "version-major" domain = "octet" label = "protocol major version"> + <doc> + The version of the protocol, expressed in protocol units of 0.1 public + versions and properly printed as two digits with a leading zero. I.e. a + protocol version of "09" represents a public version "0.9". The decimal + shift allows the correct expression of pre-1.0 protocol releases. + </doc> + <doc type = "todo"> + This field should be renamed to "protocol version". + </doc> + </field> + + <field name = "version-minor" domain = "octet" label = "protocol major version"> + <doc> + The protocol revision, expressed as an integer from 0 to 9. The use of more + than ten revisions is discouraged. The public version string is constructed + from the protocol version and revision as follows: we print the protocol + version with one decimal position, and we append the protocol revision. A + version=10 and revision=2 are printed as "1.02". + </doc> + <doc type = "todo"> + This field should be renamed to "protocol revision". + </doc> + </field> + + <field name = "server-properties" domain = "peer-properties" label = "server properties"> + <rule name = "required-fields"> + <doc> + The properties SHOULD contain at least these fields: "host", specifying the + server host name or address, "product", giving the name of the server product, + "version", giving the name of the server version, "platform", giving the name + of the operating system, "copyright", if appropriate, and "information", giving + other general information. + </doc> + <doc type = "scenario"> + Client connects to server and inspects the server properties. It checks for + the presence of the required fields. + </doc> + </rule> + </field> + + <field name = "mechanisms" domain = "longstr" label = "available security mechanisms"> + <doc> + A list of the security mechanisms that the server supports, delimited by spaces. + Currently ASL supports these mechanisms: PLAIN. + </doc> + <assert check = "notnull" /> + </field> + + <field name = "locales" domain = "longstr" label = "available message locales"> + <doc> + A list of the message locales that the server supports, delimited by spaces. The + locale defines the language in which the server will send reply texts. + </doc> + <rule name = "required-support"> + <doc> + The server MUST support at least the en_US locale. + </doc> + <doc type = "scenario"> + Client connects to server and inspects the locales field. It checks for + the presence of the required locale(s). + </doc> + </rule> + <assert check = "notnull" /> + </field> + </method> + + <method name = "start-ok" synchronous = "1" index = "11" + label = "select security mechanism and locale"> + <doc> + This method selects a SASL security mechanism. ASL uses SASL (RFC2222) to + negotiate authentication and encryption. + </doc> + + <chassis name = "server" implement = "MUST" /> + + <field name = "client-properties" domain = "peer-properties" label = "client properties"> + <rule name = "required-fields"> + <!-- This rule is not testable from the client side --> + <doc> + The properties SHOULD contain at least these fields: "product", giving the name + of the client product, "version", giving the name of the client version, "platform", + giving the name of the operating system, "copyright", if appropriate, and + "information", giving other general information. + </doc> + </rule> + </field> + + <field name = "mechanism" domain = "shortstr" label = "selected security mechanism"> + <doc> + A single security mechanisms selected by the client, which must be one of those + specified by the server. + </doc> + <rule name = "security"> + <doc> + The client SHOULD authenticate using the highest-level security profile it + can handle from the list provided by the server. + </doc> + </rule> + <rule name = "validity"> + <doc> + If the mechanism field does not contain one of the security mechanisms + proposed by the server in the Start method, the server MUST close the + connection without sending any further data. + </doc> + <doc type = "scenario"> + Client connects to server and sends an invalid security mechanism. The + server must respond by closing the connection (a socket close, with no + connection close negotiation). + </doc> + </rule> + <assert check = "notnull" /> + </field> + + <field name = "response" domain = "longstr" label = "security response data"> + <doc> + A block of opaque data passed to the security mechanism. The contents of this + data are defined by the SASL security mechanism. + </doc> + <assert check = "notnull" /> + </field> + + <field name = "locale" domain = "shortstr" label = "selected message locale"> + <doc> + A single message local selected by the client, which must be one of those + specified by the server. + </doc> + <assert check = "notnull" /> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "secure" synchronous = "1" index = "20" label = "security mechanism challenge"> + <doc> + The SASL protocol works by exchanging challenges and responses until both peers have + received sufficient information to authenticate each other. This method challenges + the client to provide more information. + </doc> + + <chassis name = "client" implement = "MUST" /> + <response name = "secure-ok" /> + + <field name = "challenge" domain = "longstr" label = "security challenge data"> + <doc> + Challenge information, a block of opaque binary data passed to the security + mechanism. + </doc> + </field> + </method> + + <method name = "secure-ok" synchronous = "1" index = "21" label = "security mechanism response"> + <doc> + This method attempts to authenticate, passing a block of SASL data for the security + mechanism at the server side. + </doc> + + <chassis name = "server" implement = "MUST" /> + + <field name = "response" domain = "longstr" label = "security response data"> + <doc> + A block of opaque data passed to the security mechanism. The contents of this + data are defined by the SASL security mechanism. + </doc> + <assert check = "notnull" /> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "tune" synchronous = "1" index = "30" + label = "propose connection tuning parameters"> + <doc> + This method proposes a set of connection configuration values to the client. The + client can accept and/or adjust these. + </doc> + + <chassis name = "client" implement = "MUST" /> + + <response name = "tune-ok" /> + + <field name = "channel-max" domain = "short" label = "proposed maximum channels"> + <doc> + The maximum total number of channels that the server allows per connection. Zero + means that the server does not impose a fixed limit, but the number of allowed + channels may be limited by available server resources. + </doc> + </field> + + <field name = "frame-max" domain = "long" label = "proposed maximum frame size"> + <doc> + The largest frame size that the server proposes for the connection. The client + can negotiate a lower value. Zero means that the server does not impose any + specific limit but may reject very large frames if it cannot allocate resources + for them. + </doc> + <rule name = "minimum"> + <doc> + Until the frame-max has been negotiated, both peers MUST accept frames of up + to frame-min-size octets large, and the minimum negotiated value for frame-max + is also frame-min-size. + </doc> + <doc type = "scenario"> + Client connects to server and sends a large properties field, creating a frame + of frame-min-size octets. The server must accept this frame. + </doc> + </rule> + </field> + + <field name = "heartbeat" domain = "short" label = "desired heartbeat delay"> + <!-- TODO 0.82 - the heartbeat negotiation mechanism was changed during + implementation because the model documented here does not actually + work properly. The best model we found is that the server proposes + a heartbeat value to the client; the client can reply with zero, meaning + 'do not use heartbeats (as documented here), or can propose its own + heartbeat value, which the server should then accept. This is different + from the model here which is disconnected - e.g. each side requests a + heartbeat independently. Basically a connection is heartbeated in + both ways, or not at all, depending on whether both peers support + heartbeating or not, and the heartbeat value should itself be chosen + by the client so that remote links can get a higher value. Also, the + actual heartbeat mechanism needs documentation, and is as follows: so + long as there is activity on a connection - in or out - both peers + assume the connection is active. When there is no activity, each peer + must send heartbeat frames. When no heartbeat frame is received after + N cycles (where N is at least 2), the connection can be considered to + have died. /PH 2006/07/19 + --> + <doc> + The delay, in seconds, of the connection heartbeat that the server wants. + Zero means the server does not want a heartbeat. + </doc> + </field> + </method> + + <method name = "tune-ok" synchronous = "1" index = "31" + label = "negotiate connection tuning parameters"> + <doc> + This method sends the client's connection tuning parameters to the server. + Certain fields are negotiated, others provide capability information. + </doc> + + <chassis name = "server" implement = "MUST" /> + + <field name = "channel-max" domain = "short" label = "negotiated maximum channels"> + <doc> + The maximum total number of channels that the client will use per connection. + </doc> + <rule name = "upper-limit"> + <doc> + If the client specifies a channel max that is higher than the value provided + by the server, the server MUST close the connection without attempting a + negotiated close. The server may report the error in some fashion to assist + implementors. + </doc> + </rule> + <assert check = "notnull" /> + <assert check = "le" method = "tune" field = "channel-max" /> + </field> + + <field name = "frame-max" domain = "long" label = "negotiated maximum frame size"> + <doc> + The largest frame size that the client and server will use for the connection. + Zero means that the client does not impose any specific limit but may reject + very large frames if it cannot allocate resources for them. Note that the + frame-max limit applies principally to content frames, where large contents can + be broken into frames of arbitrary size. + </doc> + <rule name = "minimum"> + <doc> + Until the frame-max has been negotiated, both peers MUST accept frames of up + to frame-min-size octets large, and the minimum negotiated value for frame-max + is also frame-min-size. + </doc> + </rule> + <rule name = "upper-limit"> + <doc> + If the client specifies a frame max that is higher than the value provided + by the server, the server MUST close the connection without attempting a + negotiated close. The server may report the error in some fashion to assist + implementors. + </doc> + </rule> + </field> + + <field name = "heartbeat" domain = "short" label = "desired heartbeat delay"> + <doc> + The delay, in seconds, of the connection heartbeat that the client wants. Zero + means the client does not want a heartbeat. + </doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "open" synchronous = "1" index = "40" label = "open connection to virtual host"> + <doc> + This method opens a connection to a virtual host, which is a collection of + resources, and acts to separate multiple application domains within a server. + The server may apply arbitrary limits per virtual host, such as the number + of each type of entity that may be used, per connection and/or in total. + </doc> + + <chassis name = "server" implement = "MUST" /> + <response name = "open-ok" /> + <response name = "redirect" /> + + <field name = "virtual-host" domain = "path" label = "virtual host name"> + <!-- TODO 0.82 - the entire vhost model needs review. This concept was + prompted by the HTTP vhost concept but does not fit very well into + AMQP. Currently we use the vhost as a "cluster identifier" which is + inaccurate usage. /PH 2006/07/19 + --> + <assert check = "regexp" value = "^[a-zA-Z0-9/-_]+$" /> + <doc> + The name of the virtual host to work with. + </doc> + <rule name = "separation"> + <doc> + If the server supports multiple virtual hosts, it MUST enforce a full + separation of exchanges, queues, and all associated entities per virtual + host. An application, connected to a specific virtual host, MUST NOT be able + to access resources of another virtual host. + </doc> + </rule> + <rule name = "security"> + <doc> + The server SHOULD verify that the client has permission to access the + specified virtual host. + </doc> + </rule> + </field> + + <field name = "capabilities" domain = "shortstr" label = "required capabilities"> + <doc> + The client can specify zero or more capability names, delimited by spaces. + The server can use this string to how to process the client's connection + request. + </doc> + </field> + + <field name = "insist" domain = "bit" label = "insist on connecting to server"> + <doc> + In a configuration with multiple collaborating servers, the server may respond + to a Connection.Open method with a Connection.Redirect. The insist option tells + the server that the client is insisting on a connection to the specified server. + </doc> + <rule name = "behaviour"> + <doc> + When the client uses the insist option, the server MUST NOT respond with a + Connection.Redirect method. If it cannot accept the client's connection + request it should respond by closing the connection with a suitable reply + code. + </doc> + </rule> + </field> + </method> + + <method name = "open-ok" synchronous = "1" index = "41" label = "signal that connection is ready"> + <doc> + This method signals to the client that the connection is ready for use. + </doc> + <chassis name = "client" implement = "MUST" /> + <field name = "known-hosts" domain = "known-hosts" /> + </method> + + <method name = "redirect" synchronous = "1" index = "42" label = "redirects client to other server"> + <doc> + This method redirects the client to another server, based on the requested virtual + host and/or capabilities. + </doc> + <rule name = "usage"> + <doc> + When getting the Connection.Redirect method, the client SHOULD reconnect to + the host specified, and if that host is not present, to any of the hosts + specified in the known-hosts list. + </doc> + </rule> + <chassis name = "client" implement = "MUST" /> + <field name = "host" domain = "shortstr" label = "server to connect to"> + <doc> + Specifies the server to connect to. This is an IP address or a DNS name, + optionally followed by a colon and a port number. If no port number is + specified, the client should use the default port number for the protocol. + </doc> + <assert check = "notnull" /> + </field> + <field name = "known-hosts" domain = "known-hosts" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "close" synchronous = "1" index = "50" label = "request a connection close"> + <doc> + This method indicates that the sender wants to close the connection. This may be + due to internal conditions (e.g. a forced shut-down) or due to an error handling + a specific method, i.e. an exception. When a close is due to an exception, the + sender provides the class and method id of the method which caused the exception. + </doc> + <!-- TODO: the connection close mechanism needs to be reviewed from the ODF + documentation and better expressed as rules here. /PH 2006/07/20 + --> + <rule name = "stability"> + <doc> + After sending this method any received method except the Close-OK method MUST + be discarded. + </doc> + </rule> + + <chassis name = "client" implement = "MUST" /> + <chassis name = "server" implement = "MUST" /> + <response name = "close-ok" /> + + <field name = "reply-code" domain = "reply-code" /> + <field name = "reply-text" domain = "reply-text" /> + + <field name = "class-id" domain = "class-id" label = "failing method class"> + <doc> + When the close is provoked by a method exception, this is the class of the + method. + </doc> + </field> + + <field name = "method-id" domain = "method-id" label = "failing method ID"> + <doc> + When the close is provoked by a method exception, this is the ID of the method. + </doc> + </field> + </method> + + <method name = "close-ok" synchronous = "1" index = "51" label = "confirm a connection close"> + <doc> + This method confirms a Connection.Close method and tells the recipient that it is + safe to release resources for the connection and close the socket. + </doc> + <rule name = "reporting"> + <doc> + A peer that detects a socket closure without having received a Close-Ok + handshake method SHOULD log the error. + </doc> + </rule> + <chassis name = "client" implement = "MUST" /> + <chassis name = "server" implement = "MUST" /> + </method> + </class> + + <!-- == CHANNEL ========================================================== --> + + <class name = "channel" handler = "channel" index = "20" label = "work with channels"> + <doc> + The channel class provides methods for a client to establish a channel to a + server and for both peers to operate the channel thereafter. + </doc> + + <doc type = "grammar"> + channel = open-channel *use-channel close-channel + open-channel = C:OPEN S:OPEN-OK + use-channel = C:FLOW S:FLOW-OK + / S:FLOW C:FLOW-OK + / S:ALERT + / functional-class + close-channel = C:CLOSE S:CLOSE-OK + / S:CLOSE C:CLOSE-OK + </doc> + + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "open" synchronous = "1" index = "10" label = "open a channel for use"> + <doc> + This method opens a channel to the server. + </doc> + <rule name = "state" on-failure = "channel-error"> + <doc> + The client MUST NOT use this method on an alread-opened channel. + </doc> + <doc type = "scenario"> + Client opens a channel and then reopens the same channel. + </doc> + </rule> + <chassis name = "server" implement = "MUST" /> + <response name = "open-ok" /> + <field name = "out of band" domain = "shortstr" label = "out-of-band settings"> + <doc> + Configures out-of-band transfers on this channel. The syntax and meaning of this + field will be formally defined at a later date. + </doc> + <assert check = "null" /> + </field> + </method> + + <method name = "open-ok" synchronous = "1" index = "11" label = "signal that the channel is ready"> + <doc> + This method signals to the client that the channel is ready for use. + </doc> + <chassis name = "client" implement = "MUST" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "flow" synchronous = "1" index = "20" label = "enable/disable flow from peer"> + <doc> + This method asks the peer to pause or restart the flow of content data. This is a + simple flow-control mechanism that a peer can use to avoid oveflowing its queues or + otherwise finding itself receiving more messages than it can process. Note that this + method is not intended for window control. The peer that receives a disable flow + method should finish sending the current content frame, if any, then pause. + </doc> + + <rule name = "initial-state"> + <doc> + When a new channel is opened, it is active (flow is active). Some applications + assume that channels are inactive until started. To emulate this behaviour a + client MAY open the channel, then pause it. + </doc> + </rule> + + <rule name = "bidirectional"> + <doc> + When sending content frames, a peer SHOULD monitor the channel for incoming + methods and respond to a Channel.Flow as rapidly as possible. + </doc> + </rule> + + <rule name = "throttling"> + <doc> + A peer MAY use the Channel.Flow method to throttle incoming content data for + internal reasons, for example, when exchanging data over a slower connection. + </doc> + </rule> + + <rule name = "expected-behaviour"> + <doc> + The peer that requests a Channel.Flow method MAY disconnect and/or ban a peer + that does not respect the request. This is to prevent badly-behaved clients + from overwhelming a broker. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> + + <response name = "flow-ok" /> + + <field name = "active" domain = "bit" label = "start/stop content frames"> + <doc> + If 1, the peer starts sending content frames. If 0, the peer stops sending + content frames. + </doc> + </field> + </method> + + <method name = "flow-ok" index = "21" label = "confirm a flow method"> + <doc> + Confirms to the peer that a flow command was received and processed. + </doc> + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> + <field name = "active" domain = "bit" label = "current flow setting"> + <doc> + Confirms the setting of the processed flow method: 1 means the peer will start + sending or continue to send content frames; 0 means it will not. + </doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- TODO 0.82 - remove this method entirely + /PH 2006/07/20 + --> + <method name = "alert" index = "30" label = "send a non-fatal warning message"> + <doc> + This method allows the server to send a non-fatal warning to the client. This is + used for methods that are normally asynchronous and thus do not have confirmations, + and for which the server may detect errors that need to be reported. Fatal errors + are handled as channel or connection exceptions; non-fatal errors are sent through + this method. + </doc> + <chassis name = "client" implement = "MUST" /> + <field name = "reply-code" domain = "reply-code" /> + <field name = "reply-text" domain = "reply-text" /> + <field name = "details" domain = "table" label = "detailed information for warning"> + <doc> + A set of fields that provide more information about the problem. The meaning of + these fields are defined on a per-reply-code basis (TO BE DEFINED). + </doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "close" synchronous = "1" index = "40" label = "request a channel close"> + <doc> + This method indicates that the sender wants to close the channel. This may be due to + internal conditions (e.g. a forced shut-down) or due to an error handling a specific + method, i.e. an exception. When a close is due to an exception, the sender provides + the class and method id of the method which caused the exception. + </doc> + + <!-- TODO: the channel close behaviour needs to be reviewed from the ODF + documentation and better expressed as rules here. /PH 2006/07/20 + --> + <rule name = "stability"> + <doc> + After sending this method any received method except the Close-OK method MUST + be discarded. + </doc> + </rule> + + <chassis name = "client" implement = "MUST" /> + <chassis name = "server" implement = "MUST" /> + <response name = "close-ok" /> + + <field name = "reply-code" domain = "reply-code" /> + <field name = "reply-text" domain = "reply-text" /> + + <field name = "class-id" domain = "class-id" label = "failing method class"> + <doc> + When the close is provoked by a method exception, this is the class of the + method. + </doc> + </field> + + <field name = "method-id" domain = "method-id" label = "failing method ID"> + <doc> + When the close is provoked by a method exception, this is the ID of the method. + </doc> + </field> + </method> + + <method name = "close-ok" synchronous = "1" index = "41" label = "confirm a channel close"> + <doc> + This method confirms a Channel.Close method and tells the recipient that it is safe + to release resources for the channel and close the socket. + </doc> + <rule name = "reporting"> + <doc> + A peer that detects a socket closure without having received a Channel.Close-Ok + handshake method SHOULD log the error. + </doc> + </rule> + <chassis name = "client" implement = "MUST" /> + <chassis name = "server" implement = "MUST" /> + </method> + </class> + + <!-- == ACCESS =========================================================== --> + + <!-- TODO 0.82 - this class must be implemented by two teams before we can + consider it matured. + --> + + <class name = "access" handler = "connection" index = "30" label = "work with access tickets"> + <doc> + The protocol control access to server resources using access tickets. A + client must explicitly request access tickets before doing work. An access + ticket grants a client the right to use a specific set of resources - + called a "realm" - in specific ways. + </doc> + + <doc type = "grammar"> + access = C:REQUEST S:REQUEST-OK + </doc> + + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "request" synchronous = "1" index = "10" label = "request an access ticket"> + <doc> + This method requests an access ticket for an access realm. The server + responds by granting the access ticket. If the client does not have + access rights to the requested realm this causes a connection exception. + Access tickets are a per-channel resource. + </doc> + + <chassis name = "server" implement = "MUST" /> + <response name = "request-ok" /> + + <field name = "realm" domain = "shortstr" label = "name of requested realm"> + <doc> + Specifies the name of the realm to which the client is requesting access. + The realm is a configured server-side object that collects a set of + resources (exchanges, queues, etc.). If the channel has already requested + an access ticket onto this realm, the previous ticket is destroyed and a + new ticket is created with the requested access rights, if allowed. + </doc> + <rule name = "validity" on-failure = "access-refused"> + <doc> + The client MUST specify a realm that is known to the server. The server + makes an identical response for undefined realms as it does for realms + that are defined but inaccessible to this client. + </doc> + <doc type = "scenario"> + Client specifies an undefined realm. + </doc> + </rule> + </field> + + <field name = "exclusive" domain = "bit" label = "request exclusive access"> + <doc> + Request exclusive access to the realm, meaning that this will be the only + channel that uses the realm's resources. + </doc> + <rule name = "validity" on-failure = "access-refused"> + <doc> + The client MAY NOT request exclusive access to a realm that has active + access tickets, unless the same channel already had the only access + ticket onto that realm. + </doc> + <doc type = "scenario"> + Client opens two channels and requests exclusive access to the same realm. + </doc> + </rule> + </field> + <field name = "passive" domain = "bit" label = "request passive access"> + <doc> + Request message passive access to the specified access realm. Passive + access lets a client get information about resources in the realm but + not to make any changes to them. + </doc> + </field> + <field name = "active" domain = "bit" label = "request active access"> + <doc> + Request message active access to the specified access realm. Active access lets + a client get create and delete resources in the realm. + </doc> + </field> + <field name = "write" domain = "bit" label = "request write access"> + <doc> + Request write access to the specified access realm. Write access lets a client + publish messages to all exchanges in the realm. + </doc> + </field> + <field name = "read" domain = "bit" label = "request read access"> + <doc> + Request read access to the specified access realm. Read access lets a client + consume messages from queues in the realm. + </doc> + </field> + </method> + + <method name = "request-ok" synchronous = "1" index = "11" label = "grant access to server resources"> + <doc> + This method provides the client with an access ticket. The access ticket is valid + within the current channel and for the lifespan of the channel. + </doc> + <rule name = "per-channel" on-failure = "not-allowed"> + <doc> + The client MUST NOT use access tickets except within the same channel as + originally granted. + </doc> + <doc type = "scenario"> + Client opens two channels, requests a ticket on one channel, and then + tries to use that ticket in a seconc channel. + </doc> + </rule> + <chassis name = "client" implement = "MUST" /> + <field name = "ticket" domain = "access-ticket" /> + </method> + </class> + + <!-- == EXCHANGE ========================================================= --> + + <class name = "exchange" handler = "channel" index = "40" label = "work with exchanges"> + <doc> + Exchanges match and distribute messages across queues. Exchanges can be configured in + the server or created at runtime. + </doc> + + <doc type = "grammar"> + exchange = C:DECLARE S:DECLARE-OK + / C:DELETE S:DELETE-OK + </doc> + + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> + + <rule name = "required-types"> + <doc> + The server MUST implement these standard exchange types: fanout, direct. + </doc> + <doc type = "scenario"> + Client attempts to declare an exchange with each of these standard types. + </doc> + </rule> + <rule name = "recommended-types"> + <doc> + The server SHOULD implement these standard exchange types: topic, headers. + </doc> + <doc type = "scenario"> + Client attempts to declare an exchange with each of these standard types. + </doc> + </rule> + <rule name = "required-instances"> + <doc> + The server MUST, in each virtual host, pre-declare an exchange instance + for each standard exchange type that it implements, where the name of the + exchange instance is "amq." followed by the exchange type name. + </doc> + <doc type = "scenario"> + Client creates a temporary queue and attempts to bind to each required + exchange instance (amq.fanout, amq.direct, and amq.topic, amq.headers if + those types are defined). + </doc> + </rule> + <rule name = "default-exchange"> + <doc> + The server MUST predeclare a direct exchange to act as the default exchange + for content Publish methods and for default queue bindings. + </doc> + <doc type = "scenario"> + Client checks that the default exchange is active by specifying a queue + binding with no exchange name, and publishing a message with a suitable + routing key but without specifying the exchange name, then ensuring that + the message arrives in the queue correctly. + </doc> + </rule> + <rule name = "default-access"> + <doc> + The server MUST NOT allow clients to access the default exchange except + by specifying an empty exchange name in the Queue.Bind and content Publish + methods. + </doc> + </rule> + <rule name = "extensions"> + <doc> + The server MAY implement other exchange types as wanted. + </doc> + </rule> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "declare" synchronous = "1" index = "10" label = "declare exchange, create if needed"> + <doc> + This method creates an exchange if it does not already exist, and if the exchange + exists, verifies that it is of the correct and expected class. + </doc> + <rule name = "minimum"> + <doc> + The server SHOULD support a minimum of 16 exchanges per virtual host and + ideally, impose no limit except as defined by available resources. + </doc> + <doc type = "scenario"> + The client creates as many exchanges as it can until the server reports + an error; the number of exchanges successfuly created must be at least + sixteen. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + <response name = "declare-ok" /> + + <field name = "ticket" domain = "access-ticket"> + <doc> + When a client defines a new exchange, this belongs to the access realm of the + ticket used. All further work done with that exchange must be done with an + access ticket for the same realm. + </doc> + <rule name = "validity" on-failure = "access-refused"> + <doc> + The client MUST provide a valid access ticket giving "active" access to + the realm in which the exchange exists or will be created, or "passive" + access if the if-exists flag is set. + </doc> + <doc type = "scenario"> + Client creates access ticket with wrong access rights and attempts to use + in this method. + </doc> + </rule> + </field> + + <field name = "exchange" domain = "exchange-name"> + <rule name = "reserved" on-failure = "access-refused"> + <doc> + Exchange names starting with "amq." are reserved for predeclared and + standardised exchanges. The client MUST NOT attempt to create an exchange + starting with "amq.". + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + <assert check = "regexp" value = "^[a-zA-Z0-9-_.:]+$" /> + </field> + + <field name = "type" domain = "shortstr" label = "exchange type"> + <doc> + Each exchange belongs to one of a set of exchange types implemented by the + server. The exchange types define the functionality of the exchange - i.e. how + messages are routed through it. It is not valid or meaningful to attempt to + change the type of an existing exchange. + </doc> + <rule name = "typed" on-failure = "not-allowed"> + <doc> + Exchanges cannot be redeclared with different types. The client MUST not + attempt to redeclare an existing exchange with a different type than used + in the original Exchange.Declare method. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + <rule name = "support" on-failure = "command-invalid"> + <doc> + The client MUST NOT attempt to create an exchange with a type that the + server does not support. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + <assert check = "regexp" value = "^[a-zA-Z0-9-_.:]+$" /> + </field> + + <field name = "passive" domain = "bit" label = "do not create exchange"> + <doc> + If set, the server will not create the exchange. The client can use this to + check whether an exchange exists without modifying the server state. + </doc> + <rule name = "not-found"> + <doc> + If set, and the exchange does not already exist, the server MUST raise a + channel exception with reply code 404 (not found). + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + </field> + + <field name = "durable" domain = "bit" label = "request a durable exchange"> + <doc> + If set when creating a new exchange, the exchange will be marked as durable. + Durable exchanges remain active when a server restarts. Non-durable exchanges + (transient exchanges) are purged if/when a server restarts. + </doc> + <rule name = "support"> + <doc> + The server MUST support both durable and transient exchanges. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + <rule name = "sticky"> + <doc> + The server MUST ignore the durable field if the exchange already exists. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + </field> + + <!-- TODO 0.82 - clarify how this works; there is no way to cancel a binding + except by deleting a queue. + --> + <field name = "auto-delete" domain = "bit" label = "auto-delete when unused"> + <doc> + If set, the exchange is deleted when all queues have finished using it. + </doc> + <rule name = "sticky"> + <doc> + The server MUST ignore the auto-delete field if the exchange already + exists. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + </field> + + <field name = "internal" domain = "bit" label = "create internal exchange"> + <doc> + If set, the exchange may not be used directly by publishers, but only when bound + to other exchanges. Internal exchanges are used to construct wiring that is not + visible to applications. + </doc> + </field> + + <field name = "arguments" domain = "table" label = "arguments for declaration"> + <doc> + A set of arguments for the declaration. The syntax and semantics of these + arguments depends on the server implementation. This field is ignored if passive + is 1. + </doc> + </field> + </method> + + <method name = "declare-ok" synchronous = "1" index = "11" label = "confirm exchange declaration"> + <doc> + This method confirms a Declare method and confirms the name of the exchange, + essential for automatically-named exchanges. + </doc> + <chassis name = "client" implement = "MUST" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "delete" synchronous = "1" index = "20" label = "delete an exchange"> + <doc> + This method deletes an exchange. When an exchange is deleted all queue bindings on + the exchange are cancelled. + </doc> + + <chassis name = "server" implement = "MUST" /> + <response name = "delete-ok" /> + + <field name = "ticket" domain = "access-ticket"> + <rule name = "validity" on-failure = "access-refused"> + <doc> + The client MUST provide a valid access ticket giving "active" access + rights to the exchange's access realm. + </doc> + <doc type = "scenario"> + Client creates access ticket with wrong access rights and attempts to use + in this method. + </doc> + </rule> + </field> + + <field name = "exchange" domain = "exchange-name"> + <rule name = "exists" on-failure = "not-found"> + <doc> + The client MUST NOT attempt to delete an exchange that does not exist. + </doc> + </rule> + <assert check = "notnull" /> + </field> + + <!-- TODO 0.82 - discuss whether this option is useful or not. I don't have + any real use case for it. /PH 2006-07-23. + --> + <field name = "if-unused" domain = "bit" label = "delete only if unused"> + <doc> + If set, the server will only delete the exchange if it has no queue bindings. If + the exchange has queue bindings the server does not delete it but raises a + channel exception instead. + </doc> + </field> + </method> + + <method name = "delete-ok" synchronous = "1" index = "21" + label = "confirm deletion of an exchange"> + <doc>This method confirms the deletion of an exchange.</doc> + <chassis name = "client" implement = "MUST" /> + </method> + </class> + + <!-- == QUEUE ============================================================ --> + + <class name = "queue" handler = "channel" index = "50" label = "work with queues"> + <doc> + Queues store and forward messages. Queues can be configured in the server or created at + runtime. Queues must be attached to at least one exchange in order to receive messages + from publishers. + </doc> + + <doc type = "grammar"> + queue = C:DECLARE S:DECLARE-OK + / C:BIND S:BIND-OK + / C:PURGE S:PURGE-OK + / C:DELETE S:DELETE-OK + </doc> + + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> + + <rule name = "any-content"> + <doc> + A server MUST allow any content class to be sent to any queue, in any mix, and + queue and deliver these content classes independently. Note that all methods + that fetch content off queues are specific to a given content class. + </doc> + <doc type = "scenario"> + Client creates an exchange of each standard type and several queues that + it binds to each exchange. It must then sucessfully send each of the standard + content types to each of the available queues. + </doc> + </rule> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "declare" synchronous = "1" index = "10" label = "declare queue, create if needed"> + <doc> + This method creates or checks a queue. When creating a new queue the client can + specify various properties that control the durability of the queue and its + contents, and the level of sharing for the queue. + </doc> + + <rule name = "default-binding"> + <doc> + The server MUST create a default binding for a newly-created queue to the + default exchange, which is an exchange of type 'direct'. + </doc> + <doc type = "scenario"> + Client creates a new queue, and then without explicitly binding it to an + exchange, attempts to send a message through the default exchange binding, + i.e. publish a message to the empty exchange, with the queue name as routing + key. + </doc> + </rule> + + <!-- Rule test name: was "amq_queue_35" --> + <rule name = "minimum-queues"> + <doc> + The server SHOULD support a minimum of 256 queues per virtual host and ideally, + impose no limit except as defined by available resources. + </doc> + <doc type = "scenario"> + Client attempts to create as many queues as it can until the server reports + an error. The resulting count must at least be 256. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + <response name = "declare-ok" /> + + <field name = "ticket" domain = "access-ticket"> + <doc> + When a client defines a new queue, this belongs to the access realm of the + ticket used. All further work done with that queue must be done with an access + ticket for the same realm. + </doc> + <rule name = "validity" on-failure = "access-refused"> + <doc> + The client MUST provide a valid access ticket giving "active" access to + the realm in which the queue exists or will be created. + </doc> + <doc type = "scenario"> + Client creates access ticket with wrong access rights and attempts to use + in this method. + </doc> + </rule> + </field> + + <field name = "queue" domain = "queue-name"> + <rule name = "default-name"> + <doc> + The queue name MAY be empty, in which case the server MUST create a new + queue with a unique generated name and return this to the client in the + Declare-Ok method. + </doc> + <doc type = "scenario"> + Client attempts to create several queues with an empty name. The client then + verifies that the server-assigned names are unique and different. + </doc> + </rule> + <rule name = "reserved-prefix" on-failure = "not-allowed"> + <doc> + Queue names starting with "amq." are reserved for predeclared and + standardised server queues. A client MAY NOT attempt to declare a queue with a + name that starts with "amq." and the passive option set to zero. + </doc> + <doc type = "scenario"> + A client attempts to create a queue with a name starting with "amq." and with + the passive option set to zero. + </doc> + </rule> + <assert check = "regexp" value = "^[a-zA-Z0-9-_.:]*$" /> + </field> + + <field name = "passive" domain = "bit" label = "do not create queue"> + <doc> + If set, the server will not create the queue. This field allows the client + to assert the presence of a queue without modifying the server state. + </doc> + <rule name = "passive" on-failure = "not-found"> + <doc> + The client MAY ask the server to assert that a queue exists without + creating the queue if not. If the queue does not exist, the server + treats this as a failure. + </doc> + <doc type = "scenario"> + Client declares an existing queue with the passive option and expects + the server to respond with a declare-ok. Client then attempts to declare + a non-existent queue with the passive option, and the server must close + the channel with the correct reply-code. + </doc> + </rule> + </field> + + <field name = "durable" domain = "bit" label = "request a durable queue"> + <doc> + If set when creating a new queue, the queue will be marked as durable. Durable + queues remain active when a server restarts. Non-durable queues (transient + queues) are purged if/when a server restarts. Note that durable queues do not + necessarily hold persistent messages, although it does not make sense to send + persistent messages to a transient queue. + </doc> + <!-- Rule test name: was "amq_queue_03" --> + <rule name = "persistence"> + <doc>The server MUST recreate the durable queue after a restart.</doc> + + <!-- TODO: use 'client does something' rather than 'a client does something'. --> + <doc type = "scenario"> + A client creates a durable queue. The server is then restarted. The client + then attempts to send a message to the queue. The message should be successfully + delivered. + </doc> + </rule> + <!-- Rule test name: was "amq_queue_36" --> + <rule name = "types"> + <doc>The server MUST support both durable and transient queues.</doc> + <doc type = "scenario"> + A client creates two named queues, one durable and one transient. + </doc> + </rule> + <!-- Rule test name: was "amq_queue_37" --> + <rule name = "pre-existence"> + <doc>The server MUST ignore the durable field if the queue already exists.</doc> + <doc type = "scenario"> + A client creates two named queues, one durable and one transient. The client + then attempts to declare the two queues using the same names again, but reversing + the value of the durable flag in each case. Verify that the queues still exist + with the original durable flag values. + <!-- TODO: but how? --> + </doc> + </rule> + </field> + + <field name = "exclusive" domain = "bit" label = "request an exclusive queue"> + <doc> + Exclusive queues may only be consumed from by the current connection. Setting + the 'exclusive' flag always implies 'auto-delete'. + </doc> + + <!-- Rule test name: was "amq_queue_38" --> + <rule name = "types"> + <doc> + The server MUST support both exclusive (private) and non-exclusive (shared) + queues. + </doc> + <doc type = "scenario"> + A client creates two named queues, one exclusive and one non-exclusive. + </doc> + </rule> + + <!-- Rule test name: was "amq_queue_04" --> + <rule name = "02" on-failure = "channel-error"> + <doc> + The client MAY NOT attempt to declare any existing and exclusive queue + on multiple connections. + </doc> + <doc type = "scenario"> + A client declares an exclusive named queue. A second client on a different + connection attempts to declare a queue of the same name. + </doc> + </rule> + </field> + + <field name = "auto-delete" domain = "bit" label = "auto-delete queue when unused"> + <doc> + If set, the queue is deleted when all consumers have finished using it. Last + consumer can be cancelled either explicitly or because its channel is closed. If + there was no consumer ever on the queue, it won't be deleted. + </doc> + + <!-- Rule test name: was "amq_queue_31" --> + <rule name = "pre-existence"> + <doc> + The server MUST ignore the auto-delete field if the queue already exists. + </doc> + <doc type = "scenario"> + A client creates two named queues, one as auto-delete and one explicit-delete. + The client then attempts to declare the two queues using the same names again, + but reversing the value of the auto-delete field in each case. Verify that the + queues still exist with the original auto-delete flag values. + <!-- TODO: but how? --> + </doc> + </rule> + </field> + + <field name = "arguments" domain = "table" label = "arguments for declaration"> + <doc> + A set of arguments for the declaration. The syntax and semantics of these + arguments depends on the server implementation. This field is ignored if passive + is 1. + </doc> + </field> + </method> + + <method name = "declare-ok" synchronous = "1" index = "11" label = "confirms a queue definition"> + <doc> + This method confirms a Declare method and confirms the name of the queue, essential + for automatically-named queues. + </doc> + + <chassis name = "client" implement = "MUST" /> + + <field name = "queue" domain = "queue-name"> + <doc> + Reports the name of the queue. If the server generated a queue name, this field + contains that name. + </doc> + <assert check = "notnull" /> + </field> + + <field name = "message-count" domain = "long" label = "number of messages in queue"> + <doc> + Reports the number of messages in the queue, which will be zero for + newly-created queues. + </doc> + </field> + + <field name = "consumer-count" domain = "long" label = "number of consumers"> + <doc> + Reports the number of active consumers for the queue. Note that consumers can + suspend activity (Channel.Flow) in which case they do not appear in this count. + </doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "bind" synchronous = "1" index = "20" label = "bind queue to an exchange"> + <doc> + This method binds a queue to an exchange. Until a queue is bound it will not receive + any messages. In a classic messaging model, store-and-forward queues are bound to a + dest exchange and subscription queues are bound to a dest_wild exchange. + </doc> + + <!-- Rule test name: was "amq_queue_25" --> + <rule name = "duplicates"> + <doc> + A server MUST allow ignore duplicate bindings - that is, two or more bind + methods for a specific queue, with identical arguments - without treating these + as an error. + </doc> + <doc type = "scenario"> + A client binds a named queue to an exchange. The client then repeats the bind + (with identical arguments). + </doc> + </rule> + + <!-- Rule test name: was "amq_queue_39" --> + <rule name = "failure" on-failure = "??????"> + <!-- + TODO: Find correct code. The on-failure code returned should depend on why the bind + failed. Assuming that failures owing to bad parameters are covered in the rules relating + to those parameters, the only remaining reason for a failure would be the lack of + server resorces or some internal error - such as too many queues open. Would these + cases qualify as "resource error" 506 or "internal error" 541? + --> + <doc>If a bind fails, the server MUST raise a connection exception.</doc> + <doc type = "scenario"> + TODO + </doc> + </rule> + + <!-- Rule test name: was "amq_queue_12" --> + <rule name = "transient-exchange" on-failure = "not-allowed"> + <doc> + The server MUST NOT allow a durable queue to bind to a transient exchange. + </doc> + <doc type = "scenario"> + A client creates a transient exchange. The client then declares a named durable + queue and then attempts to bind the transient exchange to the durable queue. + </doc> + </rule> + + <!-- Rule test name: was "amq_queue_13" --> + <rule name = "durable-exchange"> + <doc> + Bindings for durable queues are automatically durable and the server SHOULD + restore such bindings after a server restart. + </doc> + <doc type = "scenario"> + A server creates a named durable queue and binds it to a durable exchange. The + server is restarted. The client then attempts to use the queue/exchange combination. + </doc> + </rule> + + <!-- Rule test name: was "amq_queue_17" --> + <rule name = "internal-exchange"> + <doc> + If the client attempts to bind to an exchange that was declared as internal, the server + MUST raise a connection exception with reply code 530 (not allowed). + </doc> + <doc type = "scenario"> + A client attempts to bind a named queue to an internal exchange. + </doc> + </rule> + + <!-- Rule test name: was "amq_queue_40" --> + <rule name = "binding-count"> + <doc> + The server SHOULD support at least 4 bindings per queue, and ideally, impose no + limit except as defined by available resources. + </doc> + <doc type = "scenario"> + A client creates a named queue and attempts to bind it to 4 different non-internal + exchanges. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + + <response name = "bind-ok" /> + + <field name = "ticket" domain = "access-ticket"> + <doc> + The client provides a valid access ticket giving "active" access rights to the + queue's access realm. + </doc> + </field> + + <field name = "queue" domain = "queue-name"> + <doc> + Specifies the name of the queue to bind. If the queue name is empty, refers to + the current queue for the channel, which is the last declared queue. + </doc> + + <rule name = "empty-queue" on-failure = "not-allowed"> + <doc> + A client MUST NOT be allowed to bind a non-existent and unnamed queue (i.e. + empty queue name) to an exchange. + </doc> + <doc type = "scenario"> + A client attempts to bind with an unnamed (empty) queue name to an exchange. + </doc> + </rule> + + <!-- Rule test name: was "amq_queue_26" --> + <rule name = "queue-existence" on-failure = "not-found"> + <doc> + A client MUST NOT be allowed to bind a non-existent queue (i.e. not previously + declared) to an exchange. + </doc> + <doc type = "scenario"> + A client attempts to bind an undeclared queue name to an exchange. + </doc> + </rule> + </field> + + <field name = "exchange" domain = "exchange-name" label = "name of the exchange to bind to"> + <!-- Rule test name: was "amq_queue_14" --> + <rule name = "exchange-existence" on-failure = "not-found"> + <doc> + A client MUST NOT be allowed to bind a queue to a non-existent exchange. + </doc> + <doc type = "scenario"> + A client attempts to bind an named queue to a undeclared exchange. + </doc> + </rule> + </field> + + <field name = "routing-key" domain = "shortstr" label = "message routing key"> + <doc> + Specifies the routing key for the binding. The routing key is used for routing + messages depending on the exchange configuration. Not all exchanges use a + routing key - refer to the specific exchange documentation. If the queue name + is empty, the server uses the last queue declared on the channel. If the + routing key is also empty, the server uses this queue name for the routing + key as well. If the queue name is provided but the routing key is empty, the + server does the binding with that empty routing key. The meaning of empty + routing keys depends on the exchange implementation. + </doc> + </field> + + <field name = "arguments" domain = "table" label = "arguments for binding"> + <doc> + A set of arguments for the binding. The syntax and semantics of these arguments + depends on the exchange class. + </doc> + </field> + </method> + + <method name = "bind-ok" synchronous = "1" index = "21" label = "confirm bind successful"> + <doc>This method confirms that the bind was successful.</doc> + + <chassis name = "client" implement = "MUST" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "purge" synchronous = "1" index = "30" label = "purge a queue"> + <doc> + This method removes all messages from a queue. It does not cancel consumers. Purged + messages are deleted without any formal "undo" mechanism. + </doc> + + <!-- Rule test name: was "amq_queue_15" --> + <rule name = "01"> + <doc>A call to purge MUST result in an empty queue.</doc> + </rule> + + <!-- Rule test name: was "amq_queue_41" --> + <rule name = "02"> + <doc> + On transacted channels the server MUST not purge messages that have already been + sent to a client but not yet acknowledged. + </doc> + </rule> + + <!-- TODO: Rule split? --> + + <!-- Rule test name: was "amq_queue_42" --> + <rule name = "03"> + <doc> + The server MAY implement a purge queue or log that allows system administrators + to recover accidentally-purged messages. The server SHOULD NOT keep purged + messages in the same storage spaces as the live messages since the volumes of + purged messages may get very large. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + + <response name = "purge-ok" /> + + <field name = "ticket" domain = "access-ticket"> + <doc>The access ticket must be for the access realm that holds the queue.</doc> + + <rule name = "01"> + <doc> + The client MUST provide a valid access ticket giving "read" access rights to + the queue's access realm. Note that purging a queue is equivalent to reading + all messages and discarding them. + </doc> + </rule> + </field> + + <field name = "queue" domain = "queue-name"> + <doc> + Specifies the name of the queue to purge. If the queue name is empty, refers to + the current queue for the channel, which is the last declared queue. + </doc> + + <rule name = "01"> + <doc> + If the client did not previously declare a queue, and the queue name in this + method is empty, the server MUST raise a connection exception with reply + code 530 (not allowed). + </doc> + </rule> + + <!-- TODO Rule split? --> + + <!-- Rule test name: was "amq_queue_16" --> + <rule name = "02"> + <doc> + The queue MUST exist. Attempting to purge a non-existing queue MUST cause a + channel exception. + </doc> + </rule> + </field> + </method> + + <method name = "purge-ok" synchronous = "1" index = "31" label = "confirms a queue purge"> + <doc>This method confirms the purge of a queue.</doc> + + <chassis name = "client" implement = "MUST" /> + + <field name = "message-count" domain = "long" label = "number of messages purged"> + <doc>Reports the number of messages purged.</doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "delete" synchronous = "1" index = "40" label = "delete a queue"> + <doc> + This method deletes a queue. When a queue is deleted any pending messages are sent + to a dead-letter queue if this is defined in the server configuration, and all + consumers on the queue are cancelled. + </doc> + + <!-- TODO: Rule split? --> + + <!-- Rule test name: was "amq_queue_43" --> + <rule name = "01"> + <doc> + The server SHOULD use a dead-letter queue to hold messages that were pending on + a deleted queue, and MAY provide facilities for a system administrator to move + these messages back to an active queue. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + + <response name = "delete-ok" /> + + <field name = "ticket" domain = "access-ticket"> + <doc> + The client provides a valid access ticket giving "active" access rights to the + queue's access realm. + </doc> + </field> + + <field name = "queue" domain = "queue-name"> + <doc> + Specifies the name of the queue to delete. If the queue name is empty, refers to + the current queue for the channel, which is the last declared queue. + </doc> + + <rule name = "01"> + <doc> + If the client did not previously declare a queue, and the queue name in this + method is empty, the server MUST raise a connection exception with reply + code 530 (not allowed). + </doc> + </rule> + + <!-- Rule test name: was "amq_queue_21" --> + <rule name = "02"> + <doc> + The queue must exist. If the client attempts to delete a non-existing queue + the server MUST raise a channel exception with reply code 404 (not found). + </doc> + </rule> + </field> + + <field name = "if-unused" domain = "bit" label = "delete only if unused"> + <doc> + If set, the server will only delete the queue if it has no consumers. If the + queue has consumers the server does does not delete it but raises a channel + exception instead. + </doc> + + <!-- Rule test name: was "amq_queue_29" and "amq_queue_30" --> + <rule name = "01"> + <doc>The server MUST respect the if-unused flag when deleting a queue.</doc> + </rule> + </field> + + <field name = "if-empty" domain = "bit" label = "delete only if empty"> + <doc> + If set, the server will only delete the queue if it has no messages. + </doc> + <rule name = "01"> + <doc> + If the queue is not empty the server MUST raise a channel exception with + reply code 406 (precondition failed). + </doc> + </rule> + </field> + </method> + + <method name = "delete-ok" synchronous = "1" index = "41" label = "confirm deletion of a queue"> + <doc>This method confirms the deletion of a queue.</doc> + + <chassis name = "client" implement = "MUST" /> + + <field name = "message-count" domain = "long" label = "number of messages purged"> + <doc>Reports the number of messages purged.</doc> + </field> + </method> + </class> + + <!-- == BASIC ============================================================ --> + + <class name = "basic" handler = "channel" index = "60" label = "work with basic content"> + <doc> + The Basic class provides methods that support an industry-standard messaging model. + </doc> + + <doc type = "grammar"> + basic = C:QOS S:QOS-OK + / C:CONSUME S:CONSUME-OK + / C:CANCEL S:CANCEL-OK + / C:PUBLISH content + / S:RETURN content + / S:DELIVER content + / C:GET ( S:GET-OK content / S:GET-EMPTY ) + / C:ACK + / C:REJECT + </doc> + + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MAY" /> + + <!-- Rule test name: was "amq_basic_08" --> + <rule name = "01"> + <doc> + The server SHOULD respect the persistent property of basic messages and + SHOULD make a best-effort to hold persistent basic messages on a reliable + storage mechanism. + </doc> + <doc type = "scenario"> + Send a persistent message to queue, stop server, restart server and then + verify whether message is still present. Assumes that queues are durable. + Persistence without durable queues makes no sense. + </doc> + </rule> + + <!-- Rule test name: was "amq_basic_09" --> + <rule name = "02"> + <doc> + The server MUST NOT discard a persistent basic message in case of a queue + overflow. + </doc> + <doc type = "scenario"> + Create a queue overflow situation with persistent messages and verify that + messages do not get lost (presumably the server will write them to disk). + </doc> + </rule> + + <rule name = "03"> + <doc> + The server MAY use the Channel.Flow method to slow or stop a basic message + publisher when necessary. + </doc> + <doc type = "scenario"> + Create a queue overflow situation with non-persistent messages and verify + whether the server responds with Channel.Flow or not. Repeat with persistent + messages. + </doc> + </rule> + + <!-- Rule test name: was "amq_basic_10" --> + <rule name = "04"> + <doc> + The server MAY overflow non-persistent basic messages to persistent + storage. + </doc> + <!-- Test scenario: untestable --> + </rule> + + <rule name = "05"> + <doc> + The server MAY discard or dead-letter non-persistent basic messages on a + priority basis if the queue size exceeds some configured limit. + </doc> + <!-- Test scenario: untestable --> + </rule> + + <!-- Rule test name: was "amq_basic_11" --> + <rule name = "06"> + <doc> + The server MUST implement at least 2 priority levels for basic messages, + where priorities 0-4 and 5-9 are treated as two distinct levels. + </doc> + <doc type = "scenario"> + Send a number of priority 0 messages to a queue. Send one priority 9 + message. Consume messages from the queue and verify that the first message + received was priority 9. + </doc> + </rule> + + <rule name = "07"> + <doc> + The server MAY implement up to 10 priority levels. + </doc> + <doc type = "scenario"> + Send a number of messages with mixed priorities to a queue, so that all + priority values from 0 to 9 are exercised. A good scenario would be ten + messages in low-to-high priority. Consume from queue and verify how many + priority levels emerge. + </doc> + </rule> + + <!-- Rule test name: was "amq_basic_12" --> + <rule name = "08"> + <doc> + The server MUST deliver messages of the same priority in order irrespective of + their individual persistence. + </doc> + <doc type = "scenario"> + Send a set of messages with the same priority but different persistence + settings to a queue. Consume and verify that messages arrive in same order + as originally published. + </doc> + </rule> + + <!-- Rule test name: was "amq_basic_13" --> + <rule name = "09"> + <doc> + The server MUST support automatic acknowledgements on Basic content, i.e. + consumers with the no-ack field set to FALSE. + </doc> + <doc type = "scenario"> + Create a queue and a consumer using automatic acknowledgements. Publish + a set of messages to the queue. Consume the messages and verify that all + messages are received. + </doc> + </rule> + + <rule name = "10"> + <doc> + The server MUST support explicit acknowledgements on Basic content, i.e. + consumers with the no-ack field set to TRUE. + </doc> + <doc type = "scenario"> + Create a queue and a consumer using explicit acknowledgements. Publish a + set of messages to the queue. Consume the messages but acknowledge only + half of them. Disconnect and reconnect, and consume from the queue. + Verify that the remaining messages are received. + </doc> + </rule> + + <!-- These are the properties for a Basic content --> + + <field name = "content-type" domain = "shortstr" label = "MIME content type" /> + <field name = "content-encoding" domain = "shortstr" label = "MIME content encoding" /> + <field name = "headers" domain = "table" label = "message header field table" /> + <field name = "delivery-mode" domain = "octet" label = "non-persistent (1) or persistent (2)" /> + <field name = "priority" domain = "short" label = "message priority, 0 to 9" /> + <field name = "correlation-id" domain = "shortstr" label = "application correlation identifier" /> + <field name = "reply-to" domain = "shortstr" label = "destination to reply to" /> + <field name = "expiration" domain = "shortstr" label = "message expiration specification" /> + <field name = "timestamp" domain = "timestamp" label = "message timestamp" /> + <field name = "message-id" domain = "shortstr" label = "application message identifier" /> + <field name = "type" domain = "shortstr" label = "message type name" /> + <field name = "user-id" domain = "shortstr" label = "creating user id" /> + <field name = "app-id" domain = "shortstr" label = "creating application id" /> + <!-- This field is deprecated pending review --> + <field name = "cluster-id" domain = "shortstr" label = "intra-cluster routing identifier" /> + + <!-- Type diversity test --> + <field name = "property-bit" domain = "bit" label = "Extra property for testing only" /> + <field name = "property-octet" domain = "octet" label = "Extra property for testing only" /> + <field name = "property-short" domain = "short" label = "Extra property for testing only" /> + <field name = "property-long" domain = "long" label = "Extra property for testing only" /> + <field name = "property-longlong" domain = "longlong" label = "Extra property for testing only" /> + <field name = "property-shortstr" domain = "shortstr" label = "Extra property for testing only" /> + <field name = "property-longstr" domain = "longstr" label = "Extra property for testing only" /> + <field name = "property-timestamp" domain = "timestamp" label = "Extra property for testing only" /> + <field name = "property-table" domain = "table" label = "Extra property for testing only" /> + <field name = "property-access-ticket" domain = "access-ticket" label = "Extra property for testing only" /> + <field name = "property-class-id" domain = "class-id" label = "Extra property for testing only" /> + <field name = "property-consumer-tag" domain = "consumer-tag" label = "Extra property for testing only" /> + <field name = "property-delivery-tag" domain = "delivery-tag" label = "Extra property for testing only" /> + <field name = "property-exchange-name" domain = "exchange-name" label = "Extra property for testing only" /> + <field name = "property-known-hosts" domain = "known-hosts" label = "Extra property for testing only" /> + <field name = "property-method-id" domain = "method-id" label = "Extra property for testing only" /> + <field name = "property-no-ack" domain = "no-ack" label = "Extra property for testing only" /> + <field name = "property-no-local" domain = "no-local" label = "Extra property for testing only" /> + <field name = "property-path" domain = "path" label = "Extra property for testing only" /> + <field name = "property-peer-properties" domain = "peer-properties" label = "Extra property for testing only" /> + <field name = "property-queue-name" domain = "queue-name" label = "Extra property for testing only" /> + <field name = "property-redelivered" domain = "redelivered" label = "Extra property for testing only" /> + <field name = "property-reply-code" domain = "reply-code" label = "Extra property for testing only" /> + <field name = "property-reply-text" domain = "reply-text" label = "Extra property for testing only" /> + + <!-- Bit field test --> + <field name = "property-long-A" domain = "long" label = "Extra property for testing only" /> + <field name = "property-bit-B" domain = "bit" label = "Extra property for testing only" /> + <field name = "property-bit-C" domain = "bit" label = "Extra property for testing only" /> + <field name = "property-bit-D" domain = "bit" label = "Extra property for testing only" /> + <field name = "property-bit-E" domain = "bit" label = "Extra property for testing only" /> + <field name = "property-bit-F" domain = "bit" label = "Extra property for testing only" /> + <field name = "property-shortstr-G" domain = "shortstr" label = "Extra property for testing only" /> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "qos" synchronous = "1" index = "10" label = "specify quality of service"> + <doc> + This method requests a specific quality of service. The QoS can be specified for the + current channel or for all channels on the connection. The particular properties and + semantics of a qos method always depend on the content class semantics. Though the + qos method could in principle apply to both peers, it is currently meaningful only + for the server. + </doc> + + <chassis name = "server" implement = "MUST" /> + <response name = "qos-ok" /> + + <field name = "prefetch-size" domain = "long" label = "prefetch window in octets"> + <doc> + The client can request that messages be sent in advance so that when the client + finishes processing a message, the following message is already held locally, + rather than needing to be sent down the channel. Prefetching gives a performance + improvement. This field specifies the prefetch window size in octets. The server + will send a message in advance if it is equal to or smaller in size than the + available prefetch size (and also falls into other prefetch limits). May be set + to zero, meaning "no specific limit", although other prefetch limits may still + apply. The prefetch-size is ignored if the no-ack option is set. + </doc> + <!-- Rule test name: was "amq_basic_17" --> + <rule name = "01"> + <doc> + The server MUST ignore this setting when the client is not processing any + messages - i.e. the prefetch size does not limit the transfer of single + messages to a client, only the sending in advance of more messages while + the client still has one or more unacknowledged messages. + </doc> + <doc type = "scenario"> + Define a QoS prefetch-size limit and send a single message that exceeds + that limit. Verify that the message arrives correctly. + </doc> + </rule> + </field> + + <field name = "prefetch-count" domain = "short" label = "prefetch window in messages"> + <doc> + Specifies a prefetch window in terms of whole messages. This field may be used + in combination with the prefetch-size field; a message will only be sent in + advance if both prefetch windows (and those at the channel and connection level) + allow it. The prefetch-count is ignored if the no-ack option is set. + </doc> + <!-- Rule test name: was "amq_basic_18" --> + <rule name = "01"> + <doc> + The server may send less data in advance than allowed by the client's + specified prefetch windows but it MUST NOT send more. + </doc> + <doc type = "scenario"> + Define a QoS prefetch-size limit and a prefetch-count limit greater than + one. Send multiple messages that exceed the prefetch size. Verify that + no more than one message arrives at once. + </doc> + </rule> + </field> + + <field name = "global" domain = "bit" label = "apply to entire connection"> + <doc> + By default the QoS settings apply to the current channel only. If this field is + set, they are applied to the entire connection. + </doc> + </field> + </method> + + <method name = "qos-ok" synchronous = "1" index = "11" label = "confirm the requested qos"> + <doc> + This method tells the client that the requested QoS levels could be handled by the + server. The requested QoS applies to all active consumers until a new QoS is + defined. + </doc> + <chassis name = "client" implement = "MUST" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "consume" synchronous = "1" index = "20" label = "start a queue consumer"> + <doc> + This method asks the server to start a "consumer", which is a transient request for + messages from a specific queue. Consumers last as long as the channel they were + created on, or until the client cancels them. + </doc> + + <!-- Rule test name: was "amq_basic_01" --> + <rule name = "01"> + <doc> + The server SHOULD support at least 16 consumers per queue, and ideally, impose + no limit except as defined by available resources. + </doc> + <doc type = "scenario"> + Create a queue and create consumers on that queue until the server closes the + connection. Verify that the number of consumers created was at least sixteen + and report the total number. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + <response name = "consume-ok" /> + + <field name = "ticket" domain = "access-ticket"> + <rule name = "01" on-failure = "access-refused"> + <doc> + The client MUST provide a valid access ticket giving "read" access rights to + the realm for the queue. + </doc> + <doc type = "scenario"> + Attempt to create a consumer with an invalid (non-zero) access ticket. + </doc> + </rule> + </field> + + <field name = "queue" domain = "queue-name"> + <doc> + Specifies the name of the queue to consume from. If the queue name is null, + refers to the current queue for the channel, which is the last declared queue. + </doc> + <rule name = "01" on-failure = "not-allowed"> + <doc> + If the queue name is empty the client MUST have previously declared a + queue using this channel. + </doc> + <doc type = "scenario"> + Attempt to create a consumer with an empty queue name and no previously + declared queue on the channel. + </doc> + </rule> + </field> + + <field name = "consumer-tag" domain = "consumer-tag"> + <doc> + Specifies the identifier for the consumer. The consumer tag is local to a + connection, so two clients can use the same consumer tags. If this field is + empty the server will generate a unique tag. + </doc> + <rule name = "01" on-failure = "not-allowed"> + <doc> + The client MUST NOT specify a tag that refers to an existing consumer. + </doc> + <doc type = "scenario"> + Attempt to create two consumers with the same non-empty tag. + </doc> + </rule> + <rule name = "02" on-failure = "not-allowed"> + <doc> + The consumer tag is valid only within the channel from which the + consumer was created. I.e. a client MUST NOT create a consumer in one + channel and then use it in another. + </doc> + <doc type = "scenario"> + Attempt to create a consumer in one channel, then use in another channel, + in which consumers have also been created (to test that the server uses + unique consumer tags). + </doc> + </rule> + </field> + + <field name = "no-local" domain = "no-local" /> + + <field name = "nowait" domain = "bit" label = "do not send a reply method"> + <doc> + If set, the server will not respond to the method. The client should not wait + for a reply method. If the server could not complete the method it will raise + a channel or connection exception. + </doc> + </field> + + <field name = "bit-test-1" domain = "bit" /> + <field name = "bit-test-2" domain = "bit" /> + <field name = "bit-test-3" domain = "bit" /> + <field name = "bit-test-4" domain = "bit" /> + <field name = "bit-test-5" domain = "bit" /> + <field name = "bit-test-6" domain = "bit" /> + <field name = "bit-test-7" domain = "bit" /> + <field name = "bit-test-8" domain = "bit" /> + <field name = "bit-test-9" domain = "bit" /> + + <field name = "no-ack" domain = "short" /> + + <field name = "exclusive" domain = "bit" label = "request exclusive access"> + <doc> + Request exclusive consumer access, meaning only this consumer can access the + queue. + </doc> + <!-- Rule test name: was "amq_basic_02" --> + <rule name = "01" on-failure = "access-refused"> + <doc> + The client MAY NOT gain exclusive access to a queue that already has + active consumers. + </doc> + <doc type = "scenario"> + Open two connections to a server, and in one connection create a shared + (non-exclusive) queue and then consume from the queue. In the second + connection attempt to consume from the same queue using the exclusive + option. + </doc> + </rule> + </field> + + <field name = "priority" domain = "short" label = "consume priority"/> + </method> + + <method name = "consume-ok" synchronous = "1" index = "21" label = "confirm a new consumer"> + <doc> + The server provides the client with a consumer tag, which is used by the client + for methods called on the consumer at a later stage. + </doc> + <chassis name = "client" implement = "MUST" /> + <field name = "consumer-tag" domain = "consumer-tag"> + <doc> + Holds the consumer tag specified by the client or provided by the server. + </doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "cancel" synchronous = "1" index = "30" label = "end a queue consumer"> + <doc> + This method cancels a consumer. This does not affect already delivered + messages, but it does mean the server will not send any more messages for + that consumer. The client may receive an abitrary number of messages in + between sending the cancel method and receiving the cancel-ok reply. + </doc> + + <rule name = "01"> + <doc> + If the queue does not exist the server MUST ignore the cancel method, so + long as the consumer tag is valid for that channel. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + <response name = "cancel-ok" /> + + <field name = "consumer-tag" domain = "consumer-tag" /> + </method> + + <method name = "cancel-ok" synchronous = "1" index = "31" label = "confirm a cancelled consumer"> + <doc> + This method confirms that the cancellation was completed. + </doc> + <chassis name = "client" implement = "MUST" /> + <field name = "consumer-tag" domain = "consumer-tag" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "publish" content = "1" index = "40" label = "publish a message"> + <doc> + This method publishes a message to a specific exchange. The message will be routed + to queues as defined by the exchange configuration and distributed to any active + consumers when the transaction, if any, is committed. + </doc> + + <chassis name = "server" implement = "MUST" /> + + <field name = "ticket" domain = "access-ticket"> + <rule name = "01"> + <doc> + The client MUST provide a valid access ticket giving "write" access rights + to the access realm for the exchange. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + </field> + + <field name = "exchange" domain = "exchange-name"> + <doc> + Specifies the name of the exchange to publish to. The exchange name can be + empty, meaning the default exchange. If the exchange name is specified, and that + exchange does not exist, the server will raise a channel exception. + </doc> + + <!-- Rule test name: was "amq_basic_06" --> + <rule name = "01"> + <doc> + The server MUST accept a blank exchange name to mean the default exchange. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + + <!-- Rule test name: was "amq_basic_14" --> + <rule name = "02"> + <doc> + If the exchange was declared as an internal exchange, the server MUST raise + a channel exception with a reply code 403 (access refused). + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + + <!-- Rule test name: was "amq_basic_15" --> + <rule name = "03"> + <doc> + The exchange MAY refuse basic content in which case it MUST raise a channel + exception with reply code 540 (not implemented). + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + </field> + + <field name = "routing-key" domain = "shortstr" label = "Message routing key"> + <doc> + Specifies the routing key for the message. The routing key is used for routing + messages depending on the exchange configuration. + </doc> + </field> + + <field name = "mandatory" domain = "bit" label = "indicate mandatory routing"> + <doc> + This flag tells the server how to react if the message cannot be routed to a + queue. If this flag is set, the server will return an unroutable message with a + Return method. If this flag is zero, the server silently drops the message. + </doc> + <!-- Rule test name: was "amq_basic_07" --> + <rule name = "01"> + <doc> + The server SHOULD implement the mandatory flag. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + </field> + + <field name = "immediate" domain = "bit" label = "request immediate delivery"> + <doc> + This flag tells the server how to react if the message cannot be routed to a + queue consumer immediately. If this flag is set, the server will return an + undeliverable message with a Return method. If this flag is zero, the server + will queue the message, but with no guarantee that it will ever be consumed. + </doc> + <!-- Rule test name: was "amq_basic_16" --> + <rule name = "01"> + <doc> + The server SHOULD implement the immediate flag. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + </field> + </method> + + <method name = "return" content = "1" index = "50" label = "return a failed message"> + <doc> + This method returns an undeliverable message that was published with the "immediate" + flag set, or an unroutable message published with the "mandatory" flag set. The + reply code and text provide information about the reason that the message was + undeliverable. + </doc> + + <chassis name = "client" implement = "MUST" /> + + <field name = "reply-code" domain = "reply-code" /> + + <field name = "reply-text" domain = "reply-text" /> + + <field name = "exchange" domain = "exchange-name"> + <doc> + Specifies the name of the exchange that the message was originally published to. + </doc> + </field> + + <field name = "routing-key" domain = "shortstr" label = "Message routing key"> + <doc> + Specifies the routing key name specified when the message was published. + </doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "deliver" content = "1" index = "60" + label = "notify the client of a consumer message"> + <doc> + This method delivers a message to the client, via a consumer. In the asynchronous + message delivery model, the client starts a consumer using the Consume method, then + the server responds with Deliver methods as and when messages arrive for that + consumer. + </doc> + + <!-- Rule test name: was "amq_basic_19" --> + <rule name = "01"> + <!-- TODO: Rule split? --> + <doc> + The server SHOULD track the number of times a message has been delivered to + clients and when a message is redelivered a certain number of times - e.g. 5 + times - without being acknowledged, the server SHOULD consider the message to be + unprocessable (possibly causing client applications to abort), and move the + message to a dead letter queue. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer-tag" domain = "consumer-tag" /> + + <field name = "delivery-tag" domain = "delivery-tag" /> + + <field name = "redelivered" domain = "redelivered" /> + + <field name = "exchange" domain = "exchange-name"> + <doc> + Specifies the name of the exchange that the message was originally published to. + </doc> + </field> + + <field name = "routing-key" domain = "shortstr" label = "Message routing key"> + <doc>Specifies the routing key name specified when the message was published.</doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "get" synchronous = "1" index = "70" label = "direct access to a queue"> + <doc> + This method provides a direct access to the messages in a queue using a synchronous + dialogue that is designed for specific types of application where synchronous + functionality is more important than performance. + </doc> + + <response name = "get-ok" /> + <response name = "get-empty" /> + <chassis name = "server" implement = "MUST" /> + + <field name = "ticket" domain = "access-ticket"> + <rule name = "01"> + <doc> + The client MUST provide a valid access ticket giving "read" access rights to + the realm for the queue. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + </field> + + <field name = "queue" domain = "queue-name"> + <doc> + Specifies the name of the queue to consume from. If the queue name is null, + refers to the current queue for the channel, which is the last declared queue. + </doc> + <rule name = "01"> + <doc> + If the client did not previously declare a queue, and the queue name in this + method is empty, the server MUST raise a connection exception with reply + code 530 (not allowed). + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + </field> + + <field name = "no-ack" domain = "no-ack" /> + </method> + + <method name = "get-ok" synchronous = "1" content = "1" index = "71" + label = "provide client with a message"> + <doc> + This method delivers a message to the client following a get method. A message + delivered by 'get-ok' must be acknowledged unless the no-ack option was set in the + get method. + </doc> + + <chassis name = "client" implement = "MAY" /> + + <field name = "delivery-tag" domain = "delivery-tag" /> + + <field name = "redelivered" domain = "redelivered" /> + + <field name = "exchange" domain = "exchange-name"> + <doc> + Specifies the name of the exchange that the message was originally published to. + If empty, the message was published to the default exchange. + </doc> + </field> + + <field name = "routing-key" domain = "shortstr" label = "Message routing key"> + <doc>Specifies the routing key name specified when the message was published.</doc> + </field> + + <field name = "message-count" domain = "long" label = "number of messages pending"> + <doc> + This field reports the number of messages pending on the queue, excluding the + message being delivered. Note that this figure is indicative, not reliable, and + can change arbitrarily as messages are added to the queue and removed by other + clients. + </doc> + </field> + </method> + + <method name = "get-empty" synchronous = "1" index = "72" + label = "indicate no messages available"> + <doc> + This method tells the client that the queue has no messages available for the + client. + </doc> + + <chassis name = "client" implement = "MAY" /> + + <!-- This field is deprecated pending review --> + <field name = "cluster-id" domain = "shortstr" label = "Cluster id"> + <doc> + For use by cluster applications, should not be used by client applications. + </doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "ack" index = "80" label = "acknowledge one or more messages"> + <doc> + This method acknowledges one or more messages delivered via the Deliver or Get-Ok + methods. The client can ask to confirm a single message or a set of messages up to + and including a specific message. + </doc> + + <chassis name = "server" implement = "MUST" /> + + <field name = "delivery-tag" domain = "delivery-tag" /> + + <field name = "multiple" domain = "bit" label = "acknowledge multiple messages"> + <doc> + If set to 1, the delivery tag is treated as "up to and including", so that the + client can acknowledge multiple messages with a single method. If set to zero, + the delivery tag refers to a single message. If the multiple field is 1, and the + delivery tag is zero, tells the server to acknowledge all outstanding mesages. + </doc> + + <!-- Rule test name: was "amq_basic_20" --> + <rule name = "01"> + <doc> + The server MUST validate that a non-zero delivery-tag refers to an delivered + message, and raise a channel exception if this is not the case. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "reject" index = "90" label = "reject an incoming message"> + <doc> + This method allows a client to reject a message. It can be used to interrupt and + cancel large incoming messages, or return untreatable messages to their original + queue. + </doc> + + <!-- Rule test name: was "amq_basic_21" --> + <rule name = "01"> + <doc> + The server SHOULD be capable of accepting and process the Reject method while + sending message content with a Deliver or Get-Ok method. I.e. the server should + read and process incoming methods while sending output frames. To cancel a + partially-send content, the server sends a content body frame of size 1 (i.e. + with no data except the frame-end octet). + </doc> + </rule> + + <!-- Rule test name: was "amq_basic_22" --> + <rule name = "02"> + <doc> + The server SHOULD interpret this method as meaning that the client is unable to + process the message at this time. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + + <rule name = "03"> + <!-- TODO: Rule split? --> + <doc> + A client MUST NOT use this method as a means of selecting messages to process. A + rejected message MAY be discarded or dead-lettered, not necessarily passed to + another client. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + + <field name = "delivery-tag" domain = "delivery-tag" /> + + <field name = "requeue" domain = "bit" label = "requeue the message"> + <doc> + If this field is zero, the message will be discarded. If this bit is 1, the + server will attempt to requeue the message. + </doc> + + <!-- Rule test name: was "amq_basic_23" --> + <rule name = "01"> + <!-- TODO: Rule split? --> + <doc> + The server MUST NOT deliver the message to the same client within the + context of the current channel. The recommended strategy is to attempt to + deliver the message to an alternative consumer, and if that is not possible, + to move the message to a dead-letter queue. The server MAY use more + sophisticated tracking to hold the message on the queue and redeliver it to + the same client at a later stage. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + </field> + </method> + + <method name = "recover" index = "100" label = "redeliver unacknowledged messages"> + <doc> + This method asks the broker to redeliver all unacknowledged messages on a specified + channel. Zero or more messages may be redelivered. This method is only allowed on + non-transacted channels. + </doc> + + <rule name = "01"> + <doc> + The server MUST set the redelivered flag on all messages that are resent. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + + <rule name = "02"> + <doc> + The server MUST raise a channel exception if this is called on a transacted + channel. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + + <field name = "requeue" domain = "bit" label = "requeue the message"> + <doc> + If this field is zero, the message will be redelivered to the original + recipient. If this bit is 1, the server will attempt to requeue the message, + potentially then delivering it to an alternative subscriber. + </doc> + </field> + </method> + </class> + + <!-- == FILE ============================================================= --> + + <class name = "file" handler = "channel" index = "70" label = "work with file content"> + <doc> + The file class provides methods that support reliable file transfer. File + messages have a specific set of properties that are required for interoperability + with file transfer applications. File messages and acknowledgements are subject to + channel transactions. Note that the file class does not provide message browsing + methods; these are not compatible with the staging model. Applications that need + browsable file transfer should use Basic content and the Basic class. + </doc> + + <doc type = "grammar"> + file = C:QOS S:QOS-OK + / C:CONSUME S:CONSUME-OK + / C:CANCEL S:CANCEL-OK + / C:OPEN S:OPEN-OK C:STAGE content + / S:OPEN C:OPEN-OK S:STAGE content + / C:PUBLISH + / S:DELIVER + / S:RETURN + / C:ACK + / C:REJECT + </doc> + + <chassis name = "server" implement = "MAY" /> + <chassis name = "client" implement = "MAY" /> + + <rule name = "01"> + <doc> + The server MUST make a best-effort to hold file messages on a reliable storage + mechanism. + </doc> + </rule> + + <!-- TODO Rule implement attr inverse? --> + + <!-- TODO: Rule split? --> + + <rule name = "02"> + <doc> + The server MUST NOT discard a file message in case of a queue overflow. The server + MUST use the Channel.Flow method to slow or stop a file message publisher when + necessary. + </doc> + </rule> + + <!-- TODO: Rule split? --> + + <rule name = "03"> + <doc> + The server MUST implement at least 2 priority levels for file messages, where + priorities 0-4 and 5-9 are treated as two distinct levels. The server MAY implement + up to 10 priority levels. + </doc> + </rule> + + <rule name = "04"> + <doc> + The server MUST support both automatic and explicit acknowledgements on file + content. + </doc> + </rule> + + <!-- These are the properties for a File content --> + + <field name = "content-type" domain = "shortstr" label = "MIME content type" /> + <field name = "content-encoding" domain = "shortstr" label = "MIME content encoding" /> + <field name = "headers" domain = "table" label = "message header field table" /> + <field name = "priority" domain = "octet" label = "message priority, 0 to 9" /> + <field name = "reply-to" domain = "shortstr" label = "destination to reply to" /> + <field name = "message-id" domain = "shortstr" label = "application message identifier" /> + <field name = "filename" domain = "shortstr" label = "message filename" /> + <field name = "timestamp" domain = "timestamp" label = "message timestamp" /> + <!-- This field is deprecated pending review --> + <field name = "cluster-id" domain = "shortstr" label = "intra-cluster routing identifier" /> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "qos" synchronous = "1" index = "10" label = "specify quality of service"> + <doc> + This method requests a specific quality of service. The QoS can be specified for the + current channel or for all channels on the connection. The particular properties and + semantics of a qos method always depend on the content class semantics. Though the + qos method could in principle apply to both peers, it is currently meaningful only + for the server. + </doc> + + <chassis name = "server" implement = "MUST" /> + + <response name = "qos-ok" /> + + <field name = "prefetch-size" domain = "long" label = "prefetch window in octets"> + <doc> + The client can request that messages be sent in advance so that when the client + finishes processing a message, the following message is already held locally, + rather than needing to be sent down the channel. Prefetching gives a performance + improvement. This field specifies the prefetch window size in octets. May be set + to zero, meaning "no specific limit". Note that other prefetch limits may still + apply. The prefetch-size is ignored if the no-ack option is set. + </doc> + </field> + + <field name = "prefetch-count" domain = "short" label = "prefetch window in messages"> + <doc> + Specifies a prefetch window in terms of whole messages. This is compatible with + some file API implementations. This field may be used in combination with the + prefetch-size field; a message will only be sent in advance if both prefetch + windows (and those at the channel and connection level) allow it. The + prefetch-count is ignored if the no-ack option is set. + </doc> + + <rule name = "01"> + <!-- TODO: Rule split? --> + <doc> + The server MAY send less data in advance than allowed by the client's + specified prefetch windows but it MUST NOT send more. + </doc> + </rule> + </field> + + <field name = "global" domain = "bit" label = "apply to entire connection"> + <doc> + By default the QoS settings apply to the current channel only. If this field is + set, they are applied to the entire connection. + </doc> + </field> + </method> + + <method name = "qos-ok" synchronous = "1" index = "11" label = "confirm the requested qos"> + <doc> + This method tells the client that the requested QoS levels could be handled by the + server. The requested QoS applies to all active consumers until a new QoS is + defined. + </doc> + + <chassis name = "client" implement = "MUST" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "consume" synchronous = "1" index = "20" label = "start a queue consumer"> + <doc> + This method asks the server to start a "consumer", which is a transient request for + messages from a specific queue. Consumers last as long as the channel they were + created on, or until the client cancels them. + </doc> + + <rule name = "01"> + <doc> + The server SHOULD support at least 16 consumers per queue, unless the queue was + declared as private, and ideally, impose no limit except as defined by available + resources. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + + <response name = "consume-ok" /> + + <field name = "ticket" domain = "access-ticket"> + <rule name = "01"> + <doc> + The client MUST provide a valid access ticket giving "read" access rights to + the realm for the queue. + </doc> + </rule> + </field> + + <field name = "queue" domain = "queue-name"> + <doc> + Specifies the name of the queue to consume from. If the queue name is null, + refers to the current queue for the channel, which is the last declared queue. + </doc> + + <rule name = "01"> + <doc> + If the client did not previously declare a queue, and the queue name in this + method is empty, the server MUST raise a connection exception with reply + code 530 (not allowed). + </doc> + </rule> + </field> + + <field name = "consumer-tag" domain = "consumer-tag"> + <doc> + Specifies the identifier for the consumer. The consumer tag is local to a + connection, so two clients can use the same consumer tags. If this field is + empty the server will generate a unique tag. + </doc> + + <rule name = "01"> + <!-- TODO: Rule split? --> + <doc> + The tag MUST NOT refer to an existing consumer. If the client attempts to + create two consumers with the same non-empty tag the server MUST raise a + connection exception with reply code 530 (not allowed). + </doc> + </rule> + </field> + + <field name = "no-local" domain = "no-local" /> + + <field name = "no-ack" domain = "no-ack" /> + + <field name = "exclusive" domain = "bit" label = "request exclusive access"> + <doc> + Request exclusive consumer access, meaning only this consumer can access the + queue. + </doc> + + <!-- Rule test name: was "amq_file_00" --> + <rule name = "01"> + <doc> + If the server cannot grant exclusive access to the queue when asked, - + because there are other consumers active - it MUST raise a channel exception + with return code 405 (resource locked). + </doc> + </rule> + </field> + </method> + + <method name = "consume-ok" synchronous = "1" index = "21" label = "confirm a new consumer"> + <doc> + This method provides the client with a consumer tag which it MUST use in methods + that work with the consumer. + </doc> + + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer-tag" domain = "consumer-tag"> + <doc>Holds the consumer tag specified by the client or provided by the server.</doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "cancel" synchronous = "1" index = "30" label = "end a queue consumer"> + <doc> + This method cancels a consumer. This does not affect already delivered messages, but + it does mean the server will not send any more messages for that consumer. + </doc> + + <response name = "cancel-ok" /> + + <chassis name = "server" implement = "MUST" /> + + <field name = "consumer-tag" domain = "consumer-tag" /> + </method> + + <method name = "cancel-ok" synchronous = "1" index = "31" label = "confirm a cancelled consumer"> + <doc>This method confirms that the cancellation was completed.</doc> + + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer-tag" domain = "consumer-tag" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "open" synchronous = "1" index = "40" label = "request to start staging"> + <doc> + This method requests permission to start staging a message. Staging means sending + the message into a temporary area at the recipient end and then delivering the + message by referring to this temporary area. Staging is how the protocol handles + partial file transfers - if a message is partially staged and the connection breaks, + the next time the sender starts to stage it, it can restart from where it left off. + </doc> + + <response name = "open-ok" /> + + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> + + <field name = "identifier" domain = "shortstr" label = "staging identifier"> + <doc> + This is the staging identifier. This is an arbitrary string chosen by the + sender. For staging to work correctly the sender must use the same staging + identifier when staging the same message a second time after recovery from a + failure. A good choice for the staging identifier would be the SHA1 hash of the + message properties data (including the original filename, revised time, etc.). + </doc> + </field> + + <field name = "content-size" domain = "longlong" label = "message content size"> + <doc> + The size of the content in octets. The recipient may use this information to + allocate or check available space in advance, to avoid "disk full" errors during + staging of very large messages. + </doc> + + <rule name = "01"> + <doc> + The sender MUST accurately fill the content-size field. Zero-length content + is permitted. + </doc> + </rule> + </field> + </method> + + <method name = "open-ok" synchronous = "1" index = "41" label = "confirm staging ready"> + <doc> + This method confirms that the recipient is ready to accept staged data. If the + message was already partially-staged at a previous time the recipient will report + the number of octets already staged. + </doc> + + <response name = "stage" /> + + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> + + <field name = "staged-size" domain = "longlong" label = "already staged amount"> + <doc> + The amount of previously-staged content in octets. For a new message this will + be zero. + </doc> + + <rule name = "01"> + <doc> + The sender MUST start sending data from this octet offset in the message, + counting from zero. + </doc> + </rule> + + <rule name = "02"> + <!-- TODO: Rule split? --> + <doc> + The recipient MAY decide how long to hold partially-staged content and MAY + implement staging by always discarding partially-staged content. However if + it uses the file content type it MUST support the staging methods. + </doc> + </rule> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "stage" content = "1" index = "50" label = "stage message content"> + <doc> + This method stages the message, sending the message content to the recipient from + the octet offset specified in the Open-Ok method. + </doc> + + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "publish" index = "60" label = "publish a message"> + <doc> + This method publishes a staged file message to a specific exchange. The file message + will be routed to queues as defined by the exchange configuration and distributed to + any active consumers when the transaction, if any, is committed. + </doc> + + <chassis name = "server" implement = "MUST" /> + + <field name = "ticket" domain = "access-ticket"> + <rule name = "01"> + <doc> + The client MUST provide a valid access ticket giving "write" access rights + to the access realm for the exchange. + </doc> + </rule> + </field> + + <field name = "exchange" domain = "exchange-name"> + <doc> + Specifies the name of the exchange to publish to. The exchange name can be + empty, meaning the default exchange. If the exchange name is specified, and that + exchange does not exist, the server will raise a channel exception. + </doc> + + <rule name = "01"> + <doc> + The server MUST accept a blank exchange name to mean the default exchange. + </doc> + </rule> + + <rule name = "02"> + <doc> + If the exchange was declared as an internal exchange, the server MUST + respond with a reply code 403 (access refused) and raise a channel + exception. + </doc> + </rule> + + <!-- TODO: Rule split? --> + + <rule name = "03"> + <doc> + The exchange MAY refuse file content in which case it MUST respond with a + reply code 540 (not implemented) and raise a channel exception. + </doc> + </rule> + </field> + + <field name = "routing-key" domain = "shortstr" label = "Message routing key"> + <doc> + Specifies the routing key for the message. The routing key is used for routing + messages depending on the exchange configuration. + </doc> + </field> + + <field name = "mandatory" domain = "bit" label = "indicate mandatory routing"> + <doc> + This flag tells the server how to react if the message cannot be routed to a + queue. If this flag is set, the server will return an unroutable message with a + Return method. If this flag is zero, the server silently drops the message. + </doc> + + <!-- Rule test name: was "amq_file_00" --> + <rule name = "01"> + <doc>The server SHOULD implement the mandatory flag.</doc> + </rule> + </field> + + <field name = "immediate" domain = "bit" label = "request immediate delivery"> + <doc> + This flag tells the server how to react if the message cannot be routed to a + queue consumer immediately. If this flag is set, the server will return an + undeliverable message with a Return method. If this flag is zero, the server + will queue the message, but with no guarantee that it will ever be consumed. + </doc> + + <!-- Rule test name: was "amq_file_00" --> + <rule name = "01"> + <doc>The server SHOULD implement the immediate flag.</doc> + </rule> + </field> + + <field name = "identifier" domain = "shortstr" label = "staging identifier"> + <doc> + This is the staging identifier of the message to publish. The message must have + been staged. Note that a client can send the Publish method asynchronously + without waiting for staging to finish. + </doc> + </field> + </method> + + <method name = "return" content = "1" index = "70" label = "return a failed message"> + <doc> + This method returns an undeliverable message that was published with the "immediate" + flag set, or an unroutable message published with the "mandatory" flag set. The + reply code and text provide information about the reason that the message was + undeliverable. + </doc> + + <chassis name = "client" implement = "MUST" /> + + <field name = "reply-code" domain = "reply-code" /> + + <field name = "reply-text" domain = "reply-text" /> + + <field name = "exchange" domain = "exchange-name"> + <doc> + Specifies the name of the exchange that the message was originally published to. + </doc> + </field> + + <field name = "routing-key" domain = "shortstr" label = "Message routing key"> + <doc>Specifies the routing key name specified when the message was published.</doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "deliver" index = "80" label = "notify the client of a consumer message"> + <doc> + This method delivers a staged file message to the client, via a consumer. In the + asynchronous message delivery model, the client starts a consumer using the Consume + method, then the server responds with Deliver methods as and when messages arrive + for that consumer. + </doc> + + <rule name = "01"> + <!-- TODO: Rule split? --> + <doc> + The server SHOULD track the number of times a message has been delivered to + clients and when a message is redelivered a certain number of times - e.g. 5 + times - without being acknowledged, the server SHOULD consider the message to be + unprocessable (possibly causing client applications to abort), and move the + message to a dead letter queue. + </doc> + </rule> + + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer-tag" domain = "consumer-tag" /> + + <field name = "delivery-tag" domain = "delivery-tag" /> + + <field name = "redelivered" domain = "redelivered" /> + + <field name = "exchange" domain = "exchange-name"> + <doc> + Specifies the name of the exchange that the message was originally published to. + </doc> + </field> + + <field name = "routing-key" domain = "shortstr" label = "Message routing key"> + <doc>Specifies the routing key name specified when the message was published.</doc> + </field> + + <field name = "identifier" domain = "shortstr" label = "staging identifier"> + <doc> + This is the staging identifier of the message to deliver. The message must have + been staged. Note that a server can send the Deliver method asynchronously + without waiting for staging to finish. + </doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "ack" index = "90" label = "acknowledge one or more messages"> + <doc> + This method acknowledges one or more messages delivered via the Deliver method. The + client can ask to confirm a single message or a set of messages up to and including + a specific message. + </doc> + + <chassis name = "server" implement = "MUST" /> + + <field name = "delivery-tag" domain = "delivery-tag" /> + + <field name = "multiple" domain = "bit" label = "acknowledge multiple messages"> + <doc> + If set to 1, the delivery tag is treated as "up to and including", so that the + client can acknowledge multiple messages with a single method. If set to zero, + the delivery tag refers to a single message. If the multiple field is 1, and the + delivery tag is zero, tells the server to acknowledge all outstanding mesages. + </doc> + + <rule name = "01"> + <doc> + The server MUST validate that a non-zero delivery-tag refers to an delivered + message, and raise a channel exception if this is not the case. + </doc> + </rule> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "reject" index = "100" label = "reject an incoming message"> + <doc> + This method allows a client to reject a message. It can be used to return + untreatable messages to their original queue. Note that file content is staged + before delivery, so the client will not use this method to interrupt delivery of a + large message. + </doc> + + <rule name = "01"> + <doc> + The server SHOULD interpret this method as meaning that the client is unable to + process the message at this time. + </doc> + </rule> + + <!-- TODO: Rule split? --> + + <rule name = "02"> + <doc> + A client MUST NOT use this method as a means of selecting messages to process. A + rejected message MAY be discarded or dead-lettered, not necessarily passed to + another client. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + + <field name = "delivery-tag" domain = "delivery-tag" /> + + <field name = "requeue" domain = "bit" label = "requeue the message"> + <doc> + If this field is zero, the message will be discarded. If this bit is 1, the + server will attempt to requeue the message. + </doc> + + <rule name = "01"> + <!-- TODO: Rule split? --> + <doc> + The server MUST NOT deliver the message to the same client within the + context of the current channel. The recommended strategy is to attempt to + deliver the message to an alternative consumer, and if that is not possible, + to move the message to a dead-letter queue. The server MAY use more + sophisticated tracking to hold the message on the queue and redeliver it to + the same client at a later stage. + </doc> + </rule> + </field> + </method> + </class> + + <!-- == STREAM =========================================================== --> + + <class name = "stream" handler = "channel" index = "80" label = "work with streaming content"> + <doc> + The stream class provides methods that support multimedia streaming. The stream class + uses the following semantics: one message is one packet of data; delivery is + unacknowleged and unreliable; the consumer can specify quality of service parameters + that the server can try to adhere to; lower-priority messages may be discarded in favour + of high priority messages. + </doc> + + <doc type = "grammar"> + stream = C:QOS S:QOS-OK + / C:CONSUME S:CONSUME-OK + / C:CANCEL S:CANCEL-OK + / C:PUBLISH content + / S:RETURN + / S:DELIVER content + </doc> + + <chassis name = "server" implement = "MAY" /> + <chassis name = "client" implement = "MAY" /> + + <rule name = "01"> + <doc> + The server SHOULD discard stream messages on a priority basis if the queue size + exceeds some configured limit. + </doc> + </rule> + + <rule name = "02"> + <!-- TODO: Rule split? --> + <doc> + The server MUST implement at least 2 priority levels for stream messages, where + priorities 0-4 and 5-9 are treated as two distinct levels. The server MAY implement + up to 10 priority levels. + </doc> + </rule> + + <rule name = "03"> + <doc> + The server MUST implement automatic acknowledgements on stream content. That is, as + soon as a message is delivered to a client via a Deliver method, the server must + remove it from the queue. + </doc> + </rule> + + <!-- These are the properties for a Stream content --> + + <field name = "content-type" domain = "shortstr" label = "MIME content type" /> + <field name = "content-encoding" domain = "shortstr" label = "MIME content encoding" /> + <field name = "headers" domain = "table" label = "message header field table" /> + <field name = "priority" domain = "octet" label = "message priority, 0 to 9" /> + <field name = "timestamp" domain = "timestamp" label = "message timestamp" /> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "qos" synchronous = "1" index = "10" label = "specify quality of service"> + <doc> + This method requests a specific quality of service. The QoS can be specified for the + current channel or for all channels on the connection. The particular properties and + semantics of a qos method always depend on the content class semantics. Though the + qos method could in principle apply to both peers, it is currently meaningful only + for the server. + </doc> + + <chassis name = "server" implement = "MUST" /> + + <response name = "qos-ok" /> + + <field name = "prefetch-size" domain = "long" label = "prefetch window in octets"> + <doc> + The client can request that messages be sent in advance so that when the client + finishes processing a message, the following message is already held locally, + rather than needing to be sent down the channel. Prefetching gives a performance + improvement. This field specifies the prefetch window size in octets. May be set + to zero, meaning "no specific limit". Note that other prefetch limits may still + apply. + </doc> + </field> + + <field name = "prefetch-count" domain = "short" label = "prefetch window in messages"> + <doc> + Specifies a prefetch window in terms of whole messages. This field may be used + in combination with the prefetch-size field; a message will only be sent in + advance if both prefetch windows (and those at the channel and connection level) + allow it. + </doc> + </field> + + <field name = "consume-rate" domain = "long" label = "transfer rate in octets/second"> + <doc> + Specifies a desired transfer rate in octets per second. This is usually + determined by the application that uses the streaming data. A value of zero + means "no limit", i.e. as rapidly as possible. + </doc> + + <rule name = "01"> + <!-- TODO: Rule split? --> + <doc> + The server MAY ignore the prefetch values and consume rates, depending on + the type of stream and the ability of the server to queue and/or reply it. + The server MAY drop low-priority messages in favour of high-priority + messages. + </doc> + </rule> + </field> + + <field name = "global" domain = "bit" label = "apply to entire connection"> + <doc> + By default the QoS settings apply to the current channel only. If this field is + set, they are applied to the entire connection. + </doc> + </field> + </method> + + <method name = "qos-ok" synchronous = "1" index = "11" label = "confirm the requested qos"> + <doc> + This method tells the client that the requested QoS levels could be handled by the + server. The requested QoS applies to all active consumers until a new QoS is + defined. + </doc> + + <chassis name = "client" implement = "MUST" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "consume" synchronous = "1" index = "20" label = "start a queue consumer"> + <doc> + This method asks the server to start a "consumer", which is a transient request for + messages from a specific queue. Consumers last as long as the channel they were + created on, or until the client cancels them. + </doc> + + <rule name = "01"> + <doc> + The server SHOULD support at least 16 consumers per queue, unless the queue was + declared as private, and ideally, impose no limit except as defined by available + resources. + </doc> + </rule> + + <rule name = "02"> + <doc> + Streaming applications SHOULD use different channels to select different + streaming resolutions. AMQP makes no provision for filtering and/or transforming + streams except on the basis of priority-based selective delivery of individual + messages. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + <response name = "consume-ok" /> + + <field name = "ticket" domain = "access-ticket"> + <rule name = "01"> + <doc> + The client MUST provide a valid access ticket giving "read" access rights to + the realm for the queue. + </doc> + </rule> + </field> + + <field name = "queue" domain = "queue-name"> + <doc> + Specifies the name of the queue to consume from. If the queue name is null, + refers to the current queue for the channel, which is the last declared queue. + </doc> + + <rule name = "01"> + <doc> + If the client did not previously declare a queue, and the queue name in this + method is empty, the server MUST raise a connection exception with reply + code 530 (not allowed). + </doc> + </rule> + </field> + + <field name = "consumer-tag" domain = "consumer-tag"> + <doc> + Specifies the identifier for the consumer. The consumer tag is local to a + connection, so two clients can use the same consumer tags. If this field is + empty the server will generate a unique tag. + </doc> + + <rule name = "01"> + <!-- TODO: Rule split? --> + <doc> + The tag MUST NOT refer to an existing consumer. If the client attempts to + create two consumers with the same non-empty tag the server MUST raise a + connection exception with reply code 530 (not allowed). + </doc> + </rule> + </field> + + <field name = "no-local" domain = "no-local" /> + + <field name = "exclusive" domain = "bit" label = "request exclusive access"> + <doc> + Request exclusive consumer access, meaning only this consumer can access the + queue. + </doc> + + + <!-- Rule test name: was "amq_file_00" --> + <rule name = "01"> + <doc> + If the server cannot grant exclusive access to the queue when asked, - + because there are other consumers active - it MUST raise a channel exception + with return code 405 (resource locked). + </doc> + </rule> + </field> + </method> + + <method name = "consume-ok" synchronous = "1" index = "21" label = "confirm a new consumer"> + <doc> + This method provides the client with a consumer tag which it may use in methods that + work with the consumer. + </doc> + + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer-tag" domain = "consumer-tag"> + <doc>Holds the consumer tag specified by the client or provided by the server.</doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "cancel" synchronous = "1" index = "30" label = "end a queue consumer"> + <doc> + This method cancels a consumer. Since message delivery is asynchronous the client + may continue to receive messages for a short while after canceling a consumer. It + may process or discard these as appropriate. + </doc> + + <chassis name = "server" implement = "MUST" /> + + <response name = "cancel-ok" /> + + <field name = "consumer-tag" domain = "consumer-tag" /> + </method> + + <method name = "cancel-ok" synchronous = "1" index = "31" label = "confirm a cancelled consumer"> + <doc>This method confirms that the cancellation was completed.</doc> + + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer-tag" domain = "consumer-tag" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "publish" content = "1" index = "40" label = "publish a message"> + <doc> + This method publishes a message to a specific exchange. The message will be routed + to queues as defined by the exchange configuration and distributed to any active + consumers as appropriate. + </doc> + + <chassis name = "server" implement = "MUST" /> + + <field name = "ticket" domain = "access-ticket"> + <rule name = "01"> + <doc> + The client MUST provide a valid access ticket giving "write" access rights + to the access realm for the exchange. + </doc> + </rule> + </field> + + <field name = "exchange" domain = "exchange-name"> + <doc> + Specifies the name of the exchange to publish to. The exchange name can be + empty, meaning the default exchange. If the exchange name is specified, and that + exchange does not exist, the server will raise a channel exception. + </doc> + + <rule name = "01"> + <doc> + The server MUST accept a blank exchange name to mean the default exchange. + </doc> + </rule> + + <rule name = "02"> + <doc> + If the exchange was declared as an internal exchange, the server MUST + respond with a reply code 403 (access refused) and raise a channel + exception. + </doc> + </rule> + + <rule name = "03"> + <doc> + The exchange MAY refuse stream content in which case it MUST respond with a + reply code 540 (not implemented) and raise a channel exception. + </doc> + </rule> + </field> + + <field name = "routing-key" domain = "shortstr" label = "Message routing key"> + <doc> + Specifies the routing key for the message. The routing key is used for routing + messages depending on the exchange configuration. + </doc> + </field> + + <field name = "mandatory" domain = "bit" label = "indicate mandatory routing"> + <doc> + This flag tells the server how to react if the message cannot be routed to a + queue. If this flag is set, the server will return an unroutable message with a + Return method. If this flag is zero, the server silently drops the message. + </doc> + + <!-- Rule test name: was "amq_stream_00" --> + <rule name = "01"> + <doc>The server SHOULD implement the mandatory flag.</doc> + </rule> + </field> + + <field name = "immediate" domain = "bit" label = "request immediate delivery"> + <doc> + This flag tells the server how to react if the message cannot be routed to a + queue consumer immediately. If this flag is set, the server will return an + undeliverable message with a Return method. If this flag is zero, the server + will queue the message, but with no guarantee that it will ever be consumed. + </doc> + + <!-- Rule test name: was "amq_stream_00" --> + <rule name = "01"> + <doc>The server SHOULD implement the immediate flag.</doc> + </rule> + </field> + </method> + + <method name = "return" content = "1" index = "50" label = "return a failed message"> + <doc> + This method returns an undeliverable message that was published with the "immediate" + flag set, or an unroutable message published with the "mandatory" flag set. The + reply code and text provide information about the reason that the message was + undeliverable. + </doc> + + <chassis name = "client" implement = "MUST" /> + + <field name = "reply-code" domain = "reply-code" /> + + <field name = "reply-text" domain = "reply-text" /> + + <field name = "exchange" domain = "exchange-name"> + <doc> + Specifies the name of the exchange that the message was originally published to. + </doc> + </field> + + <field name = "routing-key" domain = "shortstr" label = "Message routing key"> + <doc>Specifies the routing key name specified when the message was published.</doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "deliver" content = "1" index = "60" + label = "notify the client of a consumer message"> + <doc> + This method delivers a message to the client, via a consumer. In the asynchronous + message delivery model, the client starts a consumer using the Consume method, then + the server responds with Deliver methods as and when messages arrive for that + consumer. + </doc> + + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer-tag" domain = "consumer-tag" /> + + <field name = "delivery-tag" domain = "delivery-tag" /> + + <field name = "exchange" domain = "exchange-name"> + <doc> + Specifies the name of the exchange that the message was originally published to. + </doc> + </field> + + <field name = "queue" domain = "queue-name"> + <doc> + Specifies the name of the queue that the message came from. Note that a single + channel can start many consumers on different queues. + </doc> + <assert check = "notnull" /> + </field> + </method> + </class> + + <!-- == TX =============================================================== --> + + <class name = "tx" handler = "channel" index = "90" label = "work with standard transactions"> + <doc> + Standard transactions provide so-called "1.5 phase commit". We can ensure that work is + never lost, but there is a chance of confirmations being lost, so that messages may be + resent. Applications that use standard transactions must be able to detect and ignore + duplicate messages. + </doc> + + <!-- TODO: Rule split? --> + + <rule name = "01"> + <doc> + An client using standard transactions SHOULD be able to track all messages received + within a reasonable period, and thus detect and reject duplicates of the same + message. It SHOULD NOT pass these to the application layer. + </doc> + </rule> + + <doc type = "grammar"> + tx = C:SELECT S:SELECT-OK + / C:COMMIT S:COMMIT-OK + / C:ROLLBACK S:ROLLBACK-OK + </doc> + + <chassis name = "server" implement = "SHOULD" /> + <chassis name = "client" implement = "MAY" /> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "select" synchronous = "1" index = "10" label = "select standard transaction mode"> + <doc> + This method sets the channel to use standard transactions. The client must use this + method at least once on a channel before using the Commit or Rollback methods. + </doc> + <chassis name = "server" implement = "MUST" /> + <response name = "select-ok" /> + </method> + + <method name = "select-ok" synchronous = "1" index = "11" label = "confirm transaction mode"> + <doc> + This method confirms to the client that the channel was successfully set to use + standard transactions. + </doc> + <chassis name = "client" implement = "MUST" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "commit" synchronous = "1" index = "20" label = "commit the current transaction"> + <doc> + This method commits all messages published and acknowledged in the current + transaction. A new transaction starts immediately after a commit. + </doc> + <chassis name = "server" implement = "MUST" /> + <response name = "commit-ok" /> + </method> + + <method name = "commit-ok" synchronous = "1" index = "21" label = "confirm a successful commit"> + <doc> + This method confirms to the client that the commit succeeded. Note that if a commit + fails, the server raises a channel exception. + </doc> + <chassis name = "client" implement = "MUST" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "rollback" synchronous = "1" index = "30" + label = "abandon the current transaction"> + <doc> + This method abandons all messages published and acknowledged in the current + transaction. A new transaction starts immediately after a rollback. + </doc> + <chassis name = "server" implement = "MUST" /> + <response name = "rollback-ok" /> + </method> + + <method name = "rollback-ok" synchronous = "1" index = "31" label = "confirm successful rollback"> + <doc> + This method confirms to the client that the rollback succeeded. Note that if an + rollback fails, the server raises a channel exception. + </doc> + <chassis name = "client" implement = "MUST" /> + </method> + </class> + + <!-- == DTX ============================================================== --> + + <class name = "dtx" handler = "channel" index = "100" label = "work with distributed transactions"> + <doc> + Distributed transactions provide so-called "2-phase commit". The AMQP distributed + transaction model supports the X-Open XA architecture and other distributed transaction + implementations. The Dtx class assumes that the server has a private communications + channel (not AMQP) to a distributed transaction coordinator. + </doc> + + <doc type = "grammar"> + dtx = C:SELECT S:SELECT-OK + C:START S:START-OK + </doc> + + <chassis name = "server" implement = "MAY" /> + <chassis name = "client" implement = "MAY" /> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "select" synchronous = "1" index = "10" label = "select standard transaction mode"> + <doc> + This method sets the channel to use distributed transactions. The client must use + this method at least once on a channel before using the Start method. + </doc> + <chassis name = "server" implement = "MUST" /> + <response name = "select-ok" /> + </method> + + <method name = "select-ok" synchronous = "1" index = "11" label = "confirm transaction mode"> + <doc> + This method confirms to the client that the channel was successfully set to use + distributed transactions. + </doc> + <chassis name = "client" implement = "MUST" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "start" synchronous = "1" index = "20" + label = "start a new distributed transaction"> + <doc> + This method starts a new distributed transaction. This must be the first method on a + new channel that uses the distributed transaction mode, before any methods that + publish or consume messages. + </doc> + <chassis name = "server" implement = "MAY" /> + <response name = "start-ok" /> + <field name = "dtx-identifier" domain = "shortstr" label = "transaction identifier"> + <doc> + The distributed transaction key. This identifies the transaction so that the + AMQP server can coordinate with the distributed transaction coordinator. + </doc> + <assert check = "notnull" /> + </field> + </method> + + <method name = "start-ok" synchronous = "1" index = "21" + label = "confirm the start of a new distributed transaction"> + <doc> + This method confirms to the client that the transaction started. Note that if a + start fails, the server raises a channel exception. + </doc> + <chassis name = "client" implement = "MUST" /> + </method> + </class> + + <!-- == TUNNEL =========================================================== --> + + <class name = "tunnel" handler = "tunnel" index = "110" label = "methods for protocol tunneling"> + <doc> + The tunnel methods are used to send blocks of binary data - which can be serialised AMQP + methods or other protocol frames - between AMQP peers. + </doc> + + <doc type = "grammar"> + tunnel = C:REQUEST + / S:REQUEST + </doc> + + <chassis name = "server" implement = "MAY" /> + <chassis name = "client" implement = "MAY" /> + + <field name = "headers" domain = "table" label = "message header field table" /> + <field name = "proxy-name" domain = "shortstr" label = "identity of tunnelling proxy" /> + <field name = "data-name" domain = "shortstr" label = "name or type of message being tunnelled" /> + <field name = "durable" domain = "octet" label = "message durability indicator" /> + <field name = "broadcast" domain = "octet" label = "message broadcast mode" /> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "request" content = "1" index = "10" label = "sends a tunnelled method"> + <doc> + This method tunnels a block of binary data, which can be an encoded + AMQP method or other data. The binary data is sent as the content for + the Tunnel.Request method. + </doc> + <chassis name = "server" implement = "MUST" /> + <field name = "meta-data" domain = "table" label = "meta data for the tunnelled block"> + <doc> + This field table holds arbitrary meta-data that the sender needs to + pass to the recipient. + </doc> + </field> + </method> + </class> +</amqp> diff --git a/qpid/gentools/xml-src/amqp-0.8.test.xml b/qpid/gentools/xml-src/amqp-0.8.test.xml new file mode 100644 index 0000000000..b0adf31828 --- /dev/null +++ b/qpid/gentools/xml-src/amqp-0.8.test.xml @@ -0,0 +1,3959 @@ +<?xml version="1.0"?> + +<!-- + Copyright Notice + ================ + (c) Copyright JPMorgan Chase Bank & Co., Cisco Systems, Inc., Envoy Technologies Inc., + iMatix Corporation, IONA\ufffd Technologies, Red Hat, Inc., + TWIST Process Innovations, and 29West Inc. 2006. All rights reserved. + + License + ======= + JPMorgan Chase Bank & Co., Cisco Systems, Inc., Envoy Technologies Inc., iMatix + Corporation, IONA\ufffd Technologies, Red Hat, Inc., TWIST Process Innovations, and + 29West Inc. (collectively, the "Authors") each hereby grants to you a worldwide, + perpetual, royalty-free, nontransferable, nonexclusive license to + (i) copy, display, and implement the Advanced Messaging Queue Protocol + ("AMQP") Specification and (ii) the Licensed Claims that are held by + the Authors, all for the purpose of implementing the Advanced Messaging + Queue Protocol Specification. Your license and any rights under this + Agreement will terminate immediately without notice from + any Author if you bring any claim, suit, demand, or action related to + the Advanced Messaging Queue Protocol Specification against any Author. + Upon termination, you shall destroy all copies of the Advanced Messaging + Queue Protocol Specification in your possession or control. + + As used hereunder, "Licensed Claims" means those claims of a patent or + patent application, throughout the world, excluding design patents and + design registrations, owned or controlled, or that can be sublicensed + without fee and in compliance with the requirements of this + Agreement, by an Author or its affiliates now or at any + future time and which would necessarily be infringed by implementation + of the Advanced Messaging Queue Protocol Specification. A claim is + necessarily infringed hereunder only when it is not possible to avoid + infringing it because there is no plausible non-infringing alternative + for implementing the required portions of the Advanced Messaging Queue + Protocol Specification. Notwithstanding the foregoing, Licensed Claims + shall not include any claims other than as set forth above even if + contained in the same patent as Licensed Claims; or that read solely + on any implementations of any portion of the Advanced Messaging Queue + Protocol Specification that are not required by the Advanced Messaging + Queue Protocol Specification, or that, if licensed, would require a + payment of royalties by the licensor to unaffiliated third parties. + Moreover, Licensed Claims shall not include (i) any enabling technologies + that may be necessary to make or use any Licensed Product but are not + themselves expressly set forth in the Advanced Messaging Queue Protocol + Specification (e.g., semiconductor manufacturing technology, compiler + technology, object oriented technology, networking technology, operating + system technology, and the like); or (ii) the implementation of other + published standards developed elsewhere and merely referred to in the + body of the Advanced Messaging Queue Protocol Specification, or + (iii) any Licensed Product and any combinations thereof the purpose or + function of which is not required for compliance with the Advanced + Messaging Queue Protocol Specification. For purposes of this definition, + the Advanced Messaging Queue Protocol Specification shall be deemed to + include both architectural and interconnection requirements essential + for interoperability and may also include supporting source code artifacts + where such architectural, interconnection requirements and source code + artifacts are expressly identified as being required or documentation to + achieve compliance with the Advanced Messaging Queue Protocol Specification. + + As used hereunder, "Licensed Products" means only those specific portions + of products (hardware, software or combinations thereof) that implement + and are compliant with all relevant portions of the Advanced Messaging + Queue Protocol Specification. + + The following disclaimers, which you hereby also acknowledge as to any + use you may make of the Advanced Messaging Queue Protocol Specification: + + THE ADVANCED MESSAGING QUEUE PROTOCOL SPECIFICATION IS PROVIDED "AS IS," + AND THE AUTHORS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR + IMPLIED, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, OR TITLE; THAT THE + CONTENTS OF THE ADVANCED MESSAGING QUEUE PROTOCOL SPECIFICATION ARE + SUITABLE FOR ANY PURPOSE; NOR THAT THE IMPLEMENTATION OF THE ADVANCED + MESSAGING QUEUE PROTOCOL SPECIFICATION WILL NOT INFRINGE ANY THIRD PARTY + PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. + + THE AUTHORS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL, + INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATING TO ANY + USE, IMPLEMENTATION OR DISTRIBUTION OF THE ADVANCED MESSAGING QUEUE + PROTOCOL SPECIFICATION. + + The name and trademarks of the Authors may NOT be used in any manner, + including advertising or publicity pertaining to the Advanced Messaging + Queue Protocol Specification or its contents without specific, written + prior permission. Title to copyright in the Advanced Messaging Queue + Protocol Specification will at all times remain with the Authors. + + No other rights are granted by implication, estoppel or otherwise. + + Upon termination of your license or rights under this Agreement, you + shall destroy all copies of the Advanced Messaging Queue Protocol + Specification in your possession or control. + + Trademarks + ========== + "JPMorgan", "JPMorgan Chase", "Chase", the JPMorgan Chase logo and the + Octagon Symbol are trademarks of JPMorgan Chase & Co. + + IMATIX and the iMatix logo are trademarks of iMatix Corporation sprl. + + IONA, IONA Technologies, and the IONA logos are trademarks of IONA + Technologies PLC and/or its subsidiaries. + + LINUX is a trademark of Linus Torvalds. RED HAT and JBOSS are registered + trademarks of Red Hat, Inc. in the US and other countries. + + Java, all Java-based trademarks and OpenOffice.org are trademarks of + Sun Microsystems, Inc. in the United States, other countries, or both. + + Other company, product, or service names may be trademarks or service + marks of others. + + Links to full AMQP specification: + ================================= + http://www.envoytech.org/spec/amq/ + http://www.iona.com/opensource/amqp/ + http://www.redhat.com/solutions/specifications/amqp/ + http://www.twiststandards.org/tiki-index.php?page=AMQ + http://www.imatix.com/amqp + +--> + +<!-- +======================================================== +EDITORS: (PH) Pieter Hintjens <ph@imatix.com> + (KvdR) Kim van der Riet <kim.vdriet@redhat.com> + +NOTE: These editors have been assigned by the AMQP working group. Please do not +edit/commit this file without consulting with one of the above editors. +======================================================== + +Revision history: + 2006-06-07 (PH) - version number changed to 0.8 to conform to public + release documentation. + + 2006-05-15 (PH) - fixed comments on queue name in basic.get to clarify + use of current queue in this method. + + 2006-05-15 (PH) - fixed comments on routing key in queue.bind to clarify + how routing key is filled when empty (to allow asynch queue.declare). + + 2006-05-11 (PH) - reset version to 0.70 so that putatitive standards + group can release 2-3 major new versions before hitting 1.0 (again). + + 2006-05-11 (PH) - TODO in documentation: cycle field in frame header + has been removed. + + 2006-05-11 (PH) - added nowait option to exchange.declare, delete, + queue.declare, delete, bind, purge, basic.consume, cancel, + file.consume, cancel, stream.consume and cancel methods. + + 2006-05-11 (PH) - removed notnull rule and added explanations on queue + name in queue.bind, purge, delete, basic.consume, cancel, file.consume, + cancel, stream.consume and cancel methods. + + 2006-05-11 (PH) - added basic.qos, file.qos, and stream.qos methods that + regroup all prefetch options from the consume methods. Also removed the + prefetch option from channel.open. + + 2006-05-11 (PH) - renumbered method indexes to show request-response + nature of methods; requests are 10, 20, 30 while responses are 11, 21, + etc. + + 2006-05-11 (PH) - removed OpenAMQ extension methods from this definition + since these are maintained seperately. + + 2006-05-26 (RG) - added Basic.Recover method to allow replay of + unacknowledged messages on a channel. + + 2006-07-03 (PH) - cosmetic clean-up of Basic.Recover comments. +--> + +<amqp major="8" minor="0" port="5672" comment="AMQ protocol 0.80"> + AMQ Protocol 0.80 + +<!-- +====================================================== +== CONSTANTS +====================================================== +--> + <constant name="frame method" value="1"/> + <constant name="frame header" value="2"/> + <constant name="frame body" value="3"/> + <constant name="frame oob method" value="4"/> + <constant name="frame oob header" value="5"/> + <constant name="frame oob body" value="6"/> + <constant name="frame trace" value="7"/> + <constant name="frame heartbeat" value="8"/> + <constant name="frame min size" value="4096"/> + <constant name="frame end" value="206"/> + <constant name="reply success" value="200"> + Indicates that the method completed successfully. This reply code is + reserved for future use - the current protocol design does not use + positive confirmation and reply codes are sent only in case of an + error. +</constant> + <constant name="not delivered" value="310" class="soft error"> + The client asked for a specific message that is no longer available. + The message was delivered to another client, or was purged from the + queue for some other reason. +</constant> + <constant name="content too large" value="311" class="soft error"> + The client attempted to transfer content larger than the server + could accept at the present time. The client may retry at a later + time. +</constant> + <constant name="connection forced" value="320" class="hard error"> + An operator intervened to close the connection for some reason. + The client may retry at some later date. +</constant> + <constant name="invalid path" value="402" class="hard error"> + The client tried to work with an unknown virtual host or cluster. +</constant> + <constant name="access refused" value="403" class="soft error"> + The client attempted to work with a server entity to which it has + no due to security settings. +</constant> + <constant name="not found" value="404" class="soft error"> + The client attempted to work with a server entity that does not exist. +</constant> + <constant name="resource locked" value="405" class="soft error"> + The client attempted to work with a server entity to which it has + no access because another client is working with it. +</constant> + <constant name="frame error" value="501" class="hard error"> + The client sent a malformed frame that the server could not decode. + This strongly implies a programming error in the client. +</constant> + <constant name="syntax error" value="502" class="hard error"> + The client sent a frame that contained illegal values for one or more + fields. This strongly implies a programming error in the client. +</constant> + <constant name="command invalid" value="503" class="hard error"> + The client sent an invalid sequence of frames, attempting to perform + an operation that was considered invalid by the server. This usually + implies a programming error in the client. +</constant> + <constant name="channel error" value="504" class="hard error"> + The client attempted to work with a channel that had not been + correctly opened. This most likely indicates a fault in the client + layer. +</constant> + <constant name="resource error" value="506" class="hard error"> + The server could not complete the method because it lacked sufficient + resources. This may be due to the client creating too many of some + type of entity. +</constant> + <constant name="not allowed" value="530" class="hard error"> + The client tried to work with some entity in a manner that is + prohibited by the server, due to security settings or by some other + criteria. +</constant> + <constant name="not implemented" value="540" class="hard error"> + The client tried to use functionality that is not implemented in the + server. +</constant> + <constant name="internal error" value="541" class="hard error"> + The server could not complete the method because of an internal error. + The server may require intervention by an operator in order to resume + normal operations. +</constant> + <!-- +====================================================== +== DOMAIN TYPES +====================================================== +--> + <domain name="access ticket" type="short"> + access ticket granted by server + <doc> + An access ticket granted by the server for a certain set of access + rights within a specific realm. Access tickets are valid within the + channel where they were created, and expire when the channel closes. + </doc> + <assert check="ne" value="0"/> + </domain> + <domain name="class id" type="short"/> + <domain name="consumer tag" type="shortstr"> + consumer tag + <doc> + Identifier for the consumer, valid within the current connection. + </doc> + <rule implement="MUST"> + The consumer tag is valid only within the channel from which the + consumer was created. I.e. a client MUST NOT create a consumer in + one channel and then use it in another. + </rule> + </domain> + <domain name="delivery tag" type="longlong"> + server-assigned delivery tag + <doc> + The server-assigned and channel-specific delivery tag + </doc> + <rule implement="MUST"> + The delivery tag is valid only within the channel from which the + message was received. I.e. a client MUST NOT receive a message on + one channel and then acknowledge it on another. + </rule> + <rule implement="MUST"> + The server MUST NOT use a zero value for delivery tags. Zero is + reserved for client use, meaning "all messages so far received". + </rule> + </domain> + <domain name="exchange name" type="shortstr"> + exchange name + <doc> + The exchange name is a client-selected string that identifies + the exchange for publish methods. Exchange names may consist + of any mixture of digits, letters, and underscores. Exchange + names are scoped by the virtual host. + </doc> + <assert check="length" value="127"/> + </domain> + <domain name="known hosts" type="shortstr"> +list of known hosts +<doc> +Specifies the list of equivalent or alternative hosts that the server +knows about, which will normally include the current server itself. +Clients can cache this information and use it when reconnecting to a +server after a failure. +</doc> + <rule implement="MAY"> +The server MAY leave this field empty if it knows of no other +hosts than itself. +</rule> + </domain> + <domain name="method id" type="short"/> + <domain name="no ack" type="bit"> + no acknowledgement needed + <doc> + If this field is set the server does not expect acknowledgments + for messages. That is, when a message is delivered to the client + the server automatically and silently acknowledges it on behalf + of the client. This functionality increases performance but at + the cost of reliability. Messages can get lost if a client dies + before it can deliver them to the application. + </doc> + </domain> + <domain name="no local" type="bit"> + do not deliver own messages + <doc> + If the no-local field is set the server will not send messages to + the client that published them. + </doc> + </domain> + <domain name="path" type="shortstr"> + <doc> + Must start with a slash "/" and continue with path names + separated by slashes. A path name consists of any combination + of at least one of [A-Za-z0-9] plus zero or more of [.-_+!=:]. +</doc> + <assert check="notnull"/> + <assert check="syntax" rule="path"/> + <assert check="length" value="127"/> + </domain> + <domain name="peer properties" type="table"> + <doc> +This string provides a set of peer properties, used for +identification, debugging, and general information. +</doc> + <rule implement="SHOULD"> +The properties SHOULD contain these fields: +"product", giving the name of the peer product, "version", giving +the name of the peer version, "platform", giving the name of the +operating system, "copyright", if appropriate, and "information", +giving other general information. +</rule> + </domain> + <domain name="queue name" type="shortstr"> + queue name + <doc> + The queue name identifies the queue within the vhost. Queue + names may consist of any mixture of digits, letters, and + underscores. + </doc> + <assert check="length" value="127"/> + </domain> + <domain name="redelivered" type="bit"> + message is being redelivered + <doc> + This indicates that the message has been previously delivered to + this or another client. + </doc> + <rule implement="SHOULD"> + The server SHOULD try to signal redelivered messages when it can. + When redelivering a message that was not successfully acknowledged, + the server SHOULD deliver it to the original client if possible. + </rule> + <rule implement="MUST"> + The client MUST NOT rely on the redelivered field but MUST take it + as a hint that the message may already have been processed. A + fully robust client must be able to track duplicate received messages + on non-transacted, and locally-transacted channels. + </rule> + </domain> + <domain name="reply code" type="short"> +reply code from server +<doc> + The reply code. The AMQ reply codes are defined in AMQ RFC 011. +</doc> + <assert check="notnull"/> + </domain> + <domain name="reply text" type="shortstr"> +localised reply text +<doc> + The localised reply text. This text can be logged as an aid to + resolving issues. +</doc> + <assert check="notnull"/> + </domain> + <class name="connection" handler="connection" index="10"> + <!-- +====================================================== +== CONNECTION +====================================================== +--> + work with socket connections +<doc> + The connection class provides methods for a client to establish a + network connection to a server, and for both peers to operate the + connection thereafter. +</doc> + <doc name="grammar"> + connection = open-connection *use-connection close-connection + open-connection = C:protocol-header + S:START C:START-OK + *challenge + S:TUNE C:TUNE-OK + C:OPEN S:OPEN-OK | S:REDIRECT + challenge = S:SECURE C:SECURE-OK + use-connection = *channel + close-connection = C:CLOSE S:CLOSE-OK + / S:CLOSE C:CLOSE-OK +</doc> + <chassis name="server" implement="MUST"/> + <chassis name="client" implement="MUST"/> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="start" synchronous="1" index="10"> + start connection negotiation + <doc> + This method starts the connection negotiation process by telling + the client the protocol version that the server proposes, along + with a list of security mechanisms which the client can use for + authentication. + </doc> + <rule implement="MUST"> + If the client cannot handle the protocol version suggested by the + server it MUST close the socket connection. + </rule> + <rule implement="MUST"> + The server MUST provide a protocol version that is lower than or + equal to that requested by the client in the protocol header. If + the server cannot support the specified protocol it MUST NOT send + this method, but MUST close the socket connection. + </rule> + <chassis name="client" implement="MUST"/> + <response name="start-ok"/> + <field name="version major" type="octet"> + protocol major version + <doc> + The protocol major version that the server agrees to use, which + cannot be higher than the client's major version. + </doc> + </field> + <field name="version minor" type="octet"> + protocol major version + <doc> + The protocol minor version that the server agrees to use, which + cannot be higher than the client's minor version. + </doc> + </field> + <field name="server properties" domain="peer properties"> + server properties + </field> + <field name="mechanisms" type="longstr"> + available security mechanisms + <doc> + A list of the security mechanisms that the server supports, delimited + by spaces. Currently ASL supports these mechanisms: PLAIN. + </doc> + <see name="security mechanisms"/> + <assert check="notnull"/> + </field> + <field name="locales" type="longstr"> + available message locales + <doc> + A list of the message locales that the server supports, delimited + by spaces. The locale defines the language in which the server + will send reply texts. + </doc> + <rule implement="MUST"> + All servers MUST support at least the en_US locale. + </rule> + <assert check="notnull"/> + </field> + </method> + <method name="start-ok" synchronous="1" index="11"> + select security mechanism and locale + <doc> + This method selects a SASL security mechanism. ASL uses SASL + (RFC2222) to negotiate authentication and encryption. + </doc> + <chassis name="server" implement="MUST"/> + <field name="client properties" domain="peer properties"> + client properties + </field> + <field name="mechanism" type="shortstr"> + selected security mechanism + <doc> + A single security mechanisms selected by the client, which must be + one of those specified by the server. + </doc> + <rule implement="SHOULD"> + The client SHOULD authenticate using the highest-level security + profile it can handle from the list provided by the server. + </rule> + <rule implement="MUST"> + The mechanism field MUST contain one of the security mechanisms + proposed by the server in the Start method. If it doesn't, the + server MUST close the socket. + </rule> + <assert check="notnull"/> + </field> + <field name="response" type="longstr"> + security response data + <doc> + A block of opaque data passed to the security mechanism. The contents + of this data are defined by the SASL security mechanism. For the + PLAIN security mechanism this is defined as a field table holding + two fields, LOGIN and PASSWORD. + </doc> + <assert check="notnull"/> + </field> + <field name="locale" type="shortstr"> + selected message locale + <doc> + A single message local selected by the client, which must be one + of those specified by the server. + </doc> + <assert check="notnull"/> + </field> + </method> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="secure" synchronous="1" index="20"> + security mechanism challenge + <doc> + The SASL protocol works by exchanging challenges and responses until + both peers have received sufficient information to authenticate each + other. This method challenges the client to provide more information. + </doc> + <chassis name="client" implement="MUST"/> + <response name="secure-ok"/> + <field name="challenge" type="longstr"> + security challenge data + <doc> + Challenge information, a block of opaque binary data passed to + the security mechanism. + </doc> + <see name="security mechanisms"/> + </field> + </method> + <method name="secure-ok" synchronous="1" index="21"> + security mechanism response + <doc> + This method attempts to authenticate, passing a block of SASL data + for the security mechanism at the server side. + </doc> + <chassis name="server" implement="MUST"/> + <field name="response" type="longstr"> + security response data + <doc> + A block of opaque data passed to the security mechanism. The contents + of this data are defined by the SASL security mechanism. + </doc> + <assert check="notnull"/> + </field> + </method> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="tune" synchronous="1" index="30"> + propose connection tuning parameters + <doc> + This method proposes a set of connection configuration values + to the client. The client can accept and/or adjust these. + </doc> + <chassis name="client" implement="MUST"/> + <response name="tune-ok"/> + <field name="channel max" type="short"> + proposed maximum channels + <doc> + The maximum total number of channels that the server allows + per connection. Zero means that the server does not impose a + fixed limit, but the number of allowed channels may be limited + by available server resources. + </doc> + </field> + <field name="frame max" type="long"> + proposed maximum frame size + <doc> + The largest frame size that the server proposes for the + connection. The client can negotiate a lower value. Zero means + that the server does not impose any specific limit but may reject + very large frames if it cannot allocate resources for them. + </doc> + <rule implement="MUST"> + Until the frame-max has been negotiated, both peers MUST accept + frames of up to 4096 octets large. The minimum non-zero value for + the frame-max field is 4096. + </rule> + </field> + <field name="heartbeat" type="short"> + desired heartbeat delay + <doc> + The delay, in seconds, of the connection heartbeat that the server + wants. Zero means the server does not want a heartbeat. + </doc> + </field> + </method> + <method name="tune-ok" synchronous="1" index="31"> + negotiate connection tuning parameters + <doc> + This method sends the client's connection tuning parameters to the + server. Certain fields are negotiated, others provide capability + information. + </doc> + <chassis name="server" implement="MUST"/> + <field name="channel max" type="short"> + negotiated maximum channels + <doc> + The maximum total number of channels that the client will use + per connection. May not be higher than the value specified by + the server. + </doc> + <rule implement="MAY"> + The server MAY ignore the channel-max value or MAY use it for + tuning its resource allocation. + </rule> + <assert check="notnull"/> + <assert check="le" method="tune" field="channel max"/> + </field> + <field name="frame max" type="long"> + negotiated maximum frame size + <doc> + The largest frame size that the client and server will use for + the connection. Zero means that the client does not impose any + specific limit but may reject very large frames if it cannot + allocate resources for them. Note that the frame-max limit + applies principally to content frames, where large contents + can be broken into frames of arbitrary size. + </doc> + <rule implement="MUST"> + Until the frame-max has been negotiated, both peers must accept + frames of up to 4096 octets large. The minimum non-zero value for + the frame-max field is 4096. + </rule> + </field> + <field name="heartbeat" type="short"> + desired heartbeat delay + <doc> + The delay, in seconds, of the connection heartbeat that the client + wants. Zero means the client does not want a heartbeat. + </doc> + </field> + </method> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="open" synchronous="1" index="40"> + open connection to virtual host + <doc> + This method opens a connection to a virtual host, which is a + collection of resources, and acts to separate multiple application + domains within a server. + </doc> + <rule implement="MUST"> + The client MUST open the context before doing any work on the + connection. + </rule> + <chassis name="server" implement="MUST"/> + <response name="open-ok"/> + <response name="redirect"/> + <field name="virtual host" domain="path"> + virtual host name + <assert check="regexp" value="^[a-zA-Z0-9/-_]+$"/> + <doc> + The name of the virtual host to work with. + </doc> + <rule implement="MUST"> + If the server supports multiple virtual hosts, it MUST enforce a + full separation of exchanges, queues, and all associated entities + per virtual host. An application, connected to a specific virtual + host, MUST NOT be able to access resources of another virtual host. + </rule> + <rule implement="SHOULD"> + The server SHOULD verify that the client has permission to access + the specified virtual host. + </rule> + <rule implement="MAY"> + The server MAY configure arbitrary limits per virtual host, such + as the number of each type of entity that may be used, per + connection and/or in total. + </rule> + </field> + <field name="capabilities" type="shortstr"> + required capabilities + <doc> + The client may specify a number of capability names, delimited by + spaces. The server can use this string to how to process the + client's connection request. + </doc> + </field> + <field name="insist" type="bit"> + insist on connecting to server + <doc> + In a configuration with multiple load-sharing servers, the server + may respond to a Connection.Open method with a Connection.Redirect. + The insist option tells the server that the client is insisting on + a connection to the specified server. + </doc> + <rule implement="SHOULD"> + When the client uses the insist option, the server SHOULD accept + the client connection unless it is technically unable to do so. + </rule> + </field> + </method> + <method name="open-ok" synchronous="1" index="41"> + signal that the connection is ready + <doc> + This method signals to the client that the connection is ready for + use. + </doc> + <chassis name="client" implement="MUST"/> + <field name="known hosts" domain="known hosts"/> + </method> + <method name="redirect" synchronous="1" index="50"> + asks the client to use a different server + <doc> + This method redirects the client to another server, based on the + requested virtual host and/or capabilities. + </doc> + <rule implement="SHOULD"> + When getting the Connection.Redirect method, the client SHOULD + reconnect to the host specified, and if that host is not present, + to any of the hosts specified in the known-hosts list. + </rule> + <chassis name="client" implement="MAY"/> + <field name="host" type="shortstr"> + server to connect to + <doc> + Specifies the server to connect to. This is an IP address or a + DNS name, optionally followed by a colon and a port number. If + no port number is specified, the client should use the default + port number for the protocol. + </doc> + <assert check="notnull"/> + </field> + <field name="known hosts" domain="known hosts"/> + </method> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="close" synchronous="1" index="60"> + request a connection close + <doc> + This method indicates that the sender wants to close the connection. + This may be due to internal conditions (e.g. a forced shut-down) or + due to an error handling a specific method, i.e. an exception. When + a close is due to an exception, the sender provides the class and + method id of the method which caused the exception. + </doc> + <rule implement="MUST"> + After sending this method any received method except the Close-OK + method MUST be discarded. + </rule> + <rule implement="MAY"> + The peer sending this method MAY use a counter or timeout to + detect failure of the other peer to respond correctly with + the Close-OK method. + </rule> + <rule implement="MUST"> + When a server receives the Close method from a client it MUST + delete all server-side resources associated with the client's + context. A client CANNOT reconnect to a context after sending + or receiving a Close method. + </rule> + <chassis name="client" implement="MUST"/> + <chassis name="server" implement="MUST"/> + <response name="close-ok"/> + <field name="reply code" domain="reply code"/> + <field name="reply text" domain="reply text"/> + <field name="class id" domain="class id"> + failing method class + <doc> + When the close is provoked by a method exception, this is the + class of the method. + </doc> + </field> + <field name="method id" domain="class id"> + failing method ID + <doc> + When the close is provoked by a method exception, this is the + ID of the method. + </doc> + </field> + </method> + <method name="close-ok" synchronous="1" index="61"> + confirm a connection close + <doc> + This method confirms a Connection.Close method and tells the + recipient that it is safe to release resources for the connection + and close the socket. + </doc> + <rule implement="SHOULD"> + A peer that detects a socket closure without having received a + Close-Ok handshake method SHOULD log the error. + </rule> + <chassis name="client" implement="MUST"/> + <chassis name="server" implement="MUST"/> + </method> + </class> + <class name="channel" handler="channel" index="20"> + <!-- +====================================================== +== CHANNEL +====================================================== +--> + work with channels +<doc> + The channel class provides methods for a client to establish a virtual + connection - a channel - to a server and for both peers to operate the + virtual connection thereafter. +</doc> + <doc name="grammar"> + channel = open-channel *use-channel close-channel + open-channel = C:OPEN S:OPEN-OK + use-channel = C:FLOW S:FLOW-OK + / S:FLOW C:FLOW-OK + / S:ALERT + / functional-class + close-channel = C:CLOSE S:CLOSE-OK + / S:CLOSE C:CLOSE-OK +</doc> + <chassis name="server" implement="MUST"/> + <chassis name="client" implement="MUST"/> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="open" synchronous="1" index="10"> + open a channel for use + <doc> + This method opens a virtual connection (a channel). + </doc> + <rule implement="MUST"> + This method MUST NOT be called when the channel is already open. + </rule> + <chassis name="server" implement="MUST"/> + <response name="open-ok"/> + <field name="out of band" type="shortstr"> + out-of-band settings + <doc> + Configures out-of-band transfers on this channel. The syntax and + meaning of this field will be formally defined at a later date. + </doc> + <assert check="null"/> + </field> + </method> + <method name="open-ok" synchronous="1" index="11"> + signal that the channel is ready + <doc> + This method signals to the client that the channel is ready for use. + </doc> + <chassis name="client" implement="MUST"/> + </method> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="flow" synchronous="1" index="20"> + enable/disable flow from peer + <doc> + This method asks the peer to pause or restart the flow of content + data. This is a simple flow-control mechanism that a peer can use + to avoid oveflowing its queues or otherwise finding itself receiving + more messages than it can process. Note that this method is not + intended for window control. The peer that receives a request to + stop sending content should finish sending the current content, if + any, and then wait until it receives a Flow restart method. + </doc> + <rule implement="MAY"> + When a new channel is opened, it is active. Some applications + assume that channels are inactive until started. To emulate this + behaviour a client MAY open the channel, then pause it. + </rule> + <rule implement="SHOULD"> + When sending content data in multiple frames, a peer SHOULD monitor + the channel for incoming methods and respond to a Channel.Flow as + rapidly as possible. + </rule> + <rule implement="MAY"> + A peer MAY use the Channel.Flow method to throttle incoming content + data for internal reasons, for example, when exchangeing data over a + slower connection. + </rule> + <rule implement="MAY"> + The peer that requests a Channel.Flow method MAY disconnect and/or + ban a peer that does not respect the request. + </rule> + <chassis name="server" implement="MUST"/> + <chassis name="client" implement="MUST"/> + <response name="flow-ok"/> + <field name="active" type="bit"> + start/stop content frames + <doc> + If 1, the peer starts sending content frames. If 0, the peer + stops sending content frames. + </doc> + </field> + </method> + <method name="flow-ok" index="21"> + confirm a flow method + <doc> + Confirms to the peer that a flow command was received and processed. + </doc> + <chassis name="server" implement="MUST"/> + <chassis name="client" implement="MUST"/> + <field name="active" type="bit"> + current flow setting + <doc> + Confirms the setting of the processed flow method: 1 means the + peer will start sending or continue to send content frames; 0 + means it will not. + </doc> + </field> + </method> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="alert" index="30"> + send a non-fatal warning message + <doc> + This method allows the server to send a non-fatal warning to the + client. This is used for methods that are normally asynchronous + and thus do not have confirmations, and for which the server may + detect errors that need to be reported. Fatal errors are handled + as channel or connection exceptions; non-fatal errors are sent + through this method. + </doc> + <chassis name="client" implement="MUST"/> + <field name="reply code" domain="reply code"/> + <field name="reply text" domain="reply text"/> + <field name="details" type="table"> + detailed information for warning + <doc> + A set of fields that provide more information about the + problem. The meaning of these fields are defined on a + per-reply-code basis (TO BE DEFINED). + </doc> + </field> + </method> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="close" synchronous="1" index="40"> + request a channel close + <doc> + This method indicates that the sender wants to close the channel. + This may be due to internal conditions (e.g. a forced shut-down) or + due to an error handling a specific method, i.e. an exception. When + a close is due to an exception, the sender provides the class and + method id of the method which caused the exception. + </doc> + <rule implement="MUST"> + After sending this method any received method except + Channel.Close-OK MUST be discarded. + </rule> + <rule implement="MAY"> + The peer sending this method MAY use a counter or timeout to detect + failure of the other peer to respond correctly with Channel.Close-OK.. + </rule> + <chassis name="client" implement="MUST"/> + <chassis name="server" implement="MUST"/> + <response name="close-ok"/> + <field name="reply code" domain="reply code"/> + <field name="reply text" domain="reply text"/> + <field name="class id" domain="class id"> + failing method class + <doc> + When the close is provoked by a method exception, this is the + class of the method. + </doc> + </field> + <field name="method id" domain="method id"> + failing method ID + <doc> + When the close is provoked by a method exception, this is the + ID of the method. + </doc> + </field> + </method> + <method name="close-ok" synchronous="1" index="41"> + confirm a channel close + <doc> + This method confirms a Channel.Close method and tells the recipient + that it is safe to release resources for the channel and close the + socket. + </doc> + <rule implement="SHOULD"> + A peer that detects a socket closure without having received a + Channel.Close-Ok handshake method SHOULD log the error. + </rule> + <chassis name="client" implement="MUST"/> + <chassis name="server" implement="MUST"/> + </method> + </class> + <class name="access" handler="connection" index="30"> + <!-- +====================================================== +== ACCESS CONTROL +====================================================== +--> + work with access tickets +<doc> + The protocol control access to server resources using access tickets. + A client must explicitly request access tickets before doing work. + An access ticket grants a client the right to use a specific set of + resources - called a "realm" - in specific ways. +</doc> + <doc name="grammar"> + access = C:REQUEST S:REQUEST-OK +</doc> + <chassis name="server" implement="MUST"/> + <chassis name="client" implement="MUST"/> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="request" synchronous="1" index="10"> + request an access ticket + <doc> + This method requests an access ticket for an access realm. + The server responds by granting the access ticket. If the + client does not have access rights to the requested realm + this causes a connection exception. Access tickets are a + per-channel resource. + </doc> + <rule implement="MUST"> + The realm name MUST start with either "/data" (for application + resources) or "/admin" (for server administration resources). + If the realm starts with any other path, the server MUST raise + a connection exception with reply code 403 (access refused). + </rule> + <rule implement="MUST"> + The server MUST implement the /data realm and MAY implement the + /admin realm. The mapping of resources to realms is not + defined in the protocol - this is a server-side configuration + issue. + </rule> + <chassis name="server" implement="MUST"/> + <response name="request-ok"/> + <field name="realm" domain="path"> + name of requested realm + <rule implement="MUST"> + If the specified realm is not known to the server, the server + must raise a channel exception with reply code 402 (invalid + path). + </rule> + </field> + <field name="exclusive" type="bit"> + request exclusive access + <doc> + Request exclusive access to the realm. If the server cannot grant + this - because there are other active tickets for the realm - it + raises a channel exception. + </doc> + </field> + <field name="passive" type="bit"> + request passive access + <doc> + Request message passive access to the specified access realm. + Passive access lets a client get information about resources in + the realm but not to make any changes to them. + </doc> + </field> + <field name="active" type="bit"> + request active access + <doc> + Request message active access to the specified access realm. + Acvtive access lets a client get create and delete resources in + the realm. + </doc> + </field> + <field name="write" type="bit"> + request write access + <doc> + Request write access to the specified access realm. Write access + lets a client publish messages to all exchanges in the realm. + </doc> + </field> + <field name="read" type="bit"> + request read access + <doc> + Request read access to the specified access realm. Read access + lets a client consume messages from queues in the realm. + </doc> + </field> + </method> + <method name="request-ok" synchronous="1" index="11"> + grant access to server resources + <doc> + This method provides the client with an access ticket. The access + ticket is valid within the current channel and for the lifespan of + the channel. + </doc> + <rule implement="MUST"> + The client MUST NOT use access tickets except within the same + channel as originally granted. + </rule> + <rule implement="MUST"> + The server MUST isolate access tickets per channel and treat an + attempt by a client to mix these as a connection exception. + </rule> + <chassis name="client" implement="MUST"/> + <field name="ticket" domain="access ticket"/> + </method> + </class> + <class name="exchange" handler="channel" index="40"> + <!-- +====================================================== +== EXCHANGES (or "routers", if you prefer) +== (Or matchers, plugins, extensions, agents,... Routing is just one of +== the many fun things an exchange can do.) +====================================================== +--> + work with exchanges +<doc> + Exchanges match and distribute messages across queues. Exchanges can be + configured in the server or created at runtime. +</doc> + <doc name="grammar"> + exchange = C:DECLARE S:DECLARE-OK + / C:DELETE S:DELETE-OK +</doc> + <chassis name="server" implement="MUST"/> + <chassis name="client" implement="MUST"/> + <rule implement="MUST"> + <test>amq_exchange_19</test> + The server MUST implement the direct and fanout exchange types, and + predeclare the corresponding exchanges named amq.direct and amq.fanout + in each virtual host. The server MUST also predeclare a direct + exchange to act as the default exchange for content Publish methods + and for default queue bindings. +</rule> + <rule implement="SHOULD"> + <test>amq_exchange_20</test> + The server SHOULD implement the topic exchange type, and predeclare + the corresponding exchange named amq.topic in each virtual host. +</rule> + <rule implement="MAY"> + <test>amq_exchange_21</test> + The server MAY implement the system exchange type, and predeclare the + corresponding exchanges named amq.system in each virtual host. If the + client attempts to bind a queue to the system exchange, the server + MUST raise a connection exception with reply code 507 (not allowed). +</rule> + <rule implement="MUST"> + <test>amq_exchange_22</test> + The default exchange MUST be defined as internal, and be inaccessible + to the client except by specifying an empty exchange name in a content + Publish method. That is, the server MUST NOT let clients make explicit + bindings to this exchange. +</rule> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="declare" synchronous="1" index="10"> + declare exchange, create if needed + <doc> + This method creates an exchange if it does not already exist, and if the + exchange exists, verifies that it is of the correct and expected class. + </doc> + <rule implement="SHOULD"> + <test>amq_exchange_23</test> + The server SHOULD support a minimum of 16 exchanges per virtual host + and ideally, impose no limit except as defined by available resources. + </rule> + <chassis name="server" implement="MUST"/> + <response name="declare-ok"/> + <field name="ticket" domain="access ticket"> + <doc> + When a client defines a new exchange, this belongs to the access realm + of the ticket used. All further work done with that exchange must be + done with an access ticket for the same realm. + </doc> + <rule implement="MUST"> + The client MUST provide a valid access ticket giving "active" access + to the realm in which the exchange exists or will be created, or + "passive" access if the if-exists flag is set. + </rule> + </field> + <field name="exchange" domain="exchange name"> + <rule implement="MUST"> + <test>amq_exchange_15</test> + Exchange names starting with "amq." are reserved for predeclared + and standardised exchanges. If the client attempts to create an + exchange starting with "amq.", the server MUST raise a channel + exception with reply code 403 (access refused). + </rule> + <assert check="regexp" value="^[a-zA-Z0-9-_.:]+$"/> + </field> + <field name="type" type="shortstr"> + exchange type + <doc> + Each exchange belongs to one of a set of exchange types implemented + by the server. The exchange types define the functionality of the + exchange - i.e. how messages are routed through it. It is not valid + or meaningful to attempt to change the type of an existing exchange. + </doc> + <rule implement="MUST"> + <test>amq_exchange_16</test> + If the exchange already exists with a different type, the server + MUST raise a connection exception with a reply code 507 (not allowed). + </rule> + <rule implement="MUST"> + <test>amq_exchange_18</test> + If the server does not support the requested exchange type it MUST + raise a connection exception with a reply code 503 (command invalid). + </rule> + <assert check="regexp" value="^[a-zA-Z0-9-_.:]+$"/> + </field> + <field name="passive" type="bit"> + do not create exchange + <doc> + If set, the server will not create the exchange. The client can use + this to check whether an exchange exists without modifying the server + state. + </doc> + <rule implement="MUST"> + <test>amq_exchange_05</test> + If set, and the exchange does not already exist, the server MUST + raise a channel exception with reply code 404 (not found). + </rule> + </field> + <field name="durable" type="bit"> + request a durable exchange + <doc> + If set when creating a new exchange, the exchange will be marked as + durable. Durable exchanges remain active when a server restarts. + Non-durable exchanges (transient exchanges) are purged if/when a + server restarts. + </doc> + <rule implement="MUST"> + <test>amq_exchange_24</test> + The server MUST support both durable and transient exchanges. + </rule> + <rule implement="MUST"> + The server MUST ignore the durable field if the exchange already + exists. + </rule> + </field> + <field name="auto delete" type="bit"> + auto-delete when unused + <doc> + If set, the exchange is deleted when all queues have finished + using it. + </doc> + <rule implement="SHOULD"> + <test>amq_exchange_02</test> + The server SHOULD allow for a reasonable delay between the point + when it determines that an exchange is not being used (or no longer + used), and the point when it deletes the exchange. At the least it + must allow a client to create an exchange and then bind a queue to + it, with a small but non-zero delay between these two actions. + </rule> + <rule implement="MUST"> + <test>amq_exchange_25</test> + The server MUST ignore the auto-delete field if the exchange already + exists. + </rule> + </field> + <field name="internal" type="bit"> + create internal exchange + <doc> + If set, the exchange may not be used directly by publishers, but + only when bound to other exchanges. Internal exchanges are used to + construct wiring that is not visible to applications. + </doc> + </field> + + <field name = "nowait" type = "bit"> + do not send a reply method + <doc> + If set, the server will not respond to the method. The client should + not wait for a reply method. If the server could not complete the + method it will raise a channel or connection exception. + </doc> + </field> + + <field name="arguments" type="table"> + arguments for declaration + <doc> + A set of arguments for the declaration. The syntax and semantics + of these arguments depends on the server implementation. This + field is ignored if passive is 1. + </doc> + </field> + </method> + <method name="declare-ok" synchronous="1" index="11"> + confirms an exchange declaration + <doc> + This method confirms a Declare method and confirms the name of the + exchange, essential for automatically-named exchanges. + </doc> + <chassis name="client" implement="MUST"/> + </method> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="delete" synchronous="1" index="20"> + delete an exchange + <doc> + This method deletes an exchange. When an exchange is deleted all queue + bindings on the exchange are cancelled. + </doc> + <chassis name="server" implement="MUST"/> + <response name="delete-ok"/> + <field name="ticket" domain="access ticket"> + <rule implement="MUST"> + The client MUST provide a valid access ticket giving "active" + access rights to the exchange's access realm. + </rule> + </field> + <field name="exchange" domain="exchange name"> + <rule implement="MUST"> + <test>amq_exchange_11</test> + The exchange MUST exist. Attempting to delete a non-existing exchange + causes a channel exception. + </rule> + <assert check="notnull"/> + </field> + <field name="if unused" type="bit"> + delete only if unused + <doc> + If set, the server will only delete the exchange if it has no queue + bindings. If the exchange has queue bindings the server does not + delete it but raises a channel exception instead. + </doc> + <rule implement="SHOULD"> + <test>amq_exchange_12</test> + If set, the server SHOULD delete the exchange but only if it has + no queue bindings. + </rule> + <rule implement="SHOULD"> + <test>amq_exchange_13</test> + If set, the server SHOULD raise a channel exception if the exchange is in + use. + </rule> + </field> + + <field name = "nowait" type = "bit"> + do not send a reply method + <doc> + If set, the server will not respond to the method. The client should + not wait for a reply method. If the server could not complete the + method it will raise a channel or connection exception. + </doc> + </field> + + </method> + <method name="delete-ok" synchronous="1" index="21"> + confirm deletion of an exchange + <doc> + This method confirms the deletion of an exchange. + </doc> + <chassis name="client" implement="MUST"/> + </method> + </class> + <class name="queue" handler="channel" index="50"> + <!-- +====================================================== +== QUEUES +====================================================== +--> + work with queues + +<doc> + Queues store and forward messages. Queues can be configured in the server + or created at runtime. Queues must be attached to at least one exchange + in order to receive messages from publishers. +</doc> + <doc name="grammar"> + queue = C:DECLARE S:DECLARE-OK + / C:BIND S:BIND-OK + / C:PURGE S:PURGE-OK + / C:DELETE S:DELETE-OK +</doc> + <chassis name="server" implement="MUST"/> + <chassis name="client" implement="MUST"/> + <rule implement="MUST"> + <test>amq_queue_33</test> + A server MUST allow any content class to be sent to any queue, in any + mix, and queue and delivery these content classes independently. Note + that all methods that fetch content off queues are specific to a given + content class. +</rule> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="declare" synchronous="1" index="10"> + declare queue, create if needed + <doc> + This method creates or checks a queue. When creating a new queue + the client can specify various properties that control the durability + of the queue and its contents, and the level of sharing for the queue. + </doc> + <rule implement="MUST"> + <test>amq_queue_34</test> + The server MUST create a default binding for a newly-created queue + to the default exchange, which is an exchange of type 'direct'. + </rule> + <rule implement="SHOULD"> + <test>amq_queue_35</test> + The server SHOULD support a minimum of 256 queues per virtual host + and ideally, impose no limit except as defined by available resources. + </rule> + <chassis name="server" implement="MUST"/> + <response name="declare-ok"/> + <field name="ticket" domain="access ticket"> + <doc> + When a client defines a new queue, this belongs to the access realm + of the ticket used. All further work done with that queue must be + done with an access ticket for the same realm. + </doc> + <doc> + The client provides a valid access ticket giving "active" access + to the realm in which the queue exists or will be created, or + "passive" access if the if-exists flag is set. + </doc> + </field> + <field name="queue" domain="queue name"> + <rule implement="MAY"> + <test>amq_queue_10</test> + The queue name MAY be empty, in which case the server MUST create + a new queue with a unique generated name and return this to the + client in the Declare-Ok method. + </rule> + <rule implement="MUST"> + <test>amq_queue_32</test> + Queue names starting with "amq." are reserved for predeclared and + standardised server queues. If the queue name starts with "amq." + and the passive option is zero, the server MUST raise a connection + exception with reply code 403 (access refused). + </rule> + <assert check="regexp" value="^[a-zA-Z0-9-_.:]*$"/> + </field> + <field name="passive" type="bit"> + do not create queue + <doc> + If set, the server will not create the queue. The client can use + this to check whether a queue exists without modifying the server + state. + </doc> + <rule implement="MUST"> + <test>amq_queue_05</test> + If set, and the queue does not already exist, the server MUST + respond with a reply code 404 (not found) and raise a channel + exception. + </rule> + </field> + <field name="durable" type="bit"> + request a durable queue + <doc> + If set when creating a new queue, the queue will be marked as + durable. Durable queues remain active when a server restarts. + Non-durable queues (transient queues) are purged if/when a + server restarts. Note that durable queues do not necessarily + hold persistent messages, although it does not make sense to + send persistent messages to a transient queue. + </doc> + <rule implement="MUST"> + <test>amq_queue_03</test> + The server MUST recreate the durable queue after a restart. + </rule> + <rule implement="MUST"> + <test>amq_queue_36</test> + The server MUST support both durable and transient queues. + </rule> + <rule implement="MUST"> + <test>amq_queue_37</test> + The server MUST ignore the durable field if the queue already + exists. + </rule> + </field> + <field name="exclusive" type="bit"> + request an exclusive queue + <doc> + Exclusive queues may only be consumed from by the current connection. + Setting the 'exclusive' flag always implies 'auto-delete'. + </doc> + <rule implement="MUST"> + <test>amq_queue_38</test> + The server MUST support both exclusive (private) and non-exclusive + (shared) queues. + </rule> + <rule implement="MUST"> + <test>amq_queue_04</test> + The server MUST raise a channel exception if 'exclusive' is specified + and the queue already exists and is owned by a different connection. + </rule> + </field> + <field name="auto delete" type="bit"> + auto-delete queue when unused + <doc> + If set, the queue is deleted when all consumers have finished + using it. Last consumer can be cancelled either explicitly or because + its channel is closed. If there was no consumer ever on the queue, it + won't be deleted. + </doc> + <rule implement="SHOULD"> + <test>amq_queue_02</test> + The server SHOULD allow for a reasonable delay between the point + when it determines that a queue is not being used (or no longer + used), and the point when it deletes the queue. At the least it + must allow a client to create a queue and then create a consumer + to read from it, with a small but non-zero delay between these + two actions. The server should equally allow for clients that may + be disconnected prematurely, and wish to re-consume from the same + queue without losing messages. We would recommend a configurable + timeout, with a suitable default value being one minute. + </rule> + <rule implement="MUST"> + <test>amq_queue_31</test> + The server MUST ignore the auto-delete field if the queue already + exists. + </rule> + </field> + <field name = "nowait" type = "bit"> + do not send a reply method + <doc> + If set, the server will not respond to the method. The client should + not wait for a reply method. If the server could not complete the + method it will raise a channel or connection exception. + </doc> + </field> + + <field name="arguments" type="table"> + arguments for declaration + <doc> + A set of arguments for the declaration. The syntax and semantics + of these arguments depends on the server implementation. This + field is ignored if passive is 1. + </doc> + </field> + </method> + <method name="declare-ok" synchronous="1" index="11"> + confirms a queue definition + <doc> + This method confirms a Declare method and confirms the name of the + queue, essential for automatically-named queues. + </doc> + <chassis name="client" implement="MUST"/> + <field name="queue" domain="queue name"> + <doc> + Reports the name of the queue. If the server generated a queue + name, this field contains that name. + </doc> + <assert check="notnull"/> + </field> + <field name="message count" type="long"> + number of messages in queue + <doc> + Reports the number of messages in the queue, which will be zero + for newly-created queues. + </doc> + </field> + <field name="consumer count" type="long"> + number of consumers + <doc> + Reports the number of active consumers for the queue. Note that + consumers can suspend activity (Channel.Flow) in which case they + do not appear in this count. + </doc> + </field> + </method> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="bind" synchronous="1" index="20"> + bind queue to an exchange + <doc> + This method binds a queue to an exchange. Until a queue is + bound it will not receive any messages. In a classic messaging + model, store-and-forward queues are bound to a dest exchange + and subscription queues are bound to a dest_wild exchange. + </doc> + <rule implement="MUST"> + <test>amq_queue_25</test> + A server MUST allow ignore duplicate bindings - that is, two or + more bind methods for a specific queue, with identical arguments + - without treating these as an error. + </rule> + <rule implement="MUST"> + <test>amq_queue_39</test> + If a bind fails, the server MUST raise a connection exception. + </rule> + <rule implement="MUST"> + <test>amq_queue_12</test> + The server MUST NOT allow a durable queue to bind to a transient + exchange. If the client attempts this the server MUST raise a + channel exception. + </rule> + <rule implement="SHOULD"> + <test>amq_queue_13</test> + Bindings for durable queues are automatically durable and the + server SHOULD restore such bindings after a server restart. + </rule> + <rule implement="MUST"> + <test>amq_queue_17</test> + If the client attempts to an exchange that was declared as internal, + the server MUST raise a connection exception with reply code 530 + (not allowed). + </rule> + <rule implement="SHOULD"> + <test>amq_queue_40</test> + The server SHOULD support at least 4 bindings per queue, and + ideally, impose no limit except as defined by available resources. + </rule> + <chassis name="server" implement="MUST"/> + <response name="bind-ok"/> + <field name="ticket" domain="access ticket"> + <doc> + The client provides a valid access ticket giving "active" + access rights to the queue's access realm. + </doc> + </field> + + <field name = "queue" domain = "queue name"> + <doc> + Specifies the name of the queue to bind. If the queue name is + empty, refers to the current queue for the channel, which is + the last declared queue. + </doc> + <doc name = "rule"> + If the client did not previously declare a queue, and the queue + name in this method is empty, the server MUST raise a connection + exception with reply code 530 (not allowed). + </doc> + <doc name = "rule" test = "amq_queue_26"> + If the queue does not exist the server MUST raise a channel exception + with reply code 404 (not found). + </doc> + </field> + + <field name="exchange" domain="exchange name"> + The name of the exchange to bind to. + <rule implement="MUST"> + <test>amq_queue_14</test> + If the exchange does not exist the server MUST raise a channel + exception with reply code 404 (not found). + </rule> + </field> + <field name="routing key" type="shortstr"> + message routing key + <doc> + Specifies the routing key for the binding. The routing key is + used for routing messages depending on the exchange configuration. + Not all exchanges use a routing key - refer to the specific + exchange documentation. If the routing key is empty and the queue + name is empty, the routing key will be the current queue for the + channel, which is the last declared queue. + </doc> + </field> + + <field name = "nowait" type = "bit"> + do not send a reply method + <doc> + If set, the server will not respond to the method. The client should + not wait for a reply method. If the server could not complete the + method it will raise a channel or connection exception. + </doc> + </field> + + <field name="arguments" type="table"> + arguments for binding + <doc> + A set of arguments for the binding. The syntax and semantics of + these arguments depends on the exchange class. + </doc> + </field> + </method> + <method name="bind-ok" synchronous="1" index="21"> + confirm bind successful + <doc> + This method confirms that the bind was successful. + </doc> + <chassis name="client" implement="MUST"/> + </method> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="purge" synchronous="1" index="30"> + purge a queue + <doc> + This method removes all messages from a queue. It does not cancel + consumers. Purged messages are deleted without any formal "undo" + mechanism. + </doc> + <rule implement="MUST"> + <test>amq_queue_15</test> + A call to purge MUST result in an empty queue. + </rule> + <rule implement="MUST"> + <test>amq_queue_41</test> + On transacted channels the server MUST not purge messages that have + already been sent to a client but not yet acknowledged. + </rule> + <rule implement="MAY"> + <test>amq_queue_42</test> + The server MAY implement a purge queue or log that allows system + administrators to recover accidentally-purged messages. The server + SHOULD NOT keep purged messages in the same storage spaces as the + live messages since the volumes of purged messages may get very + large. + </rule> + <chassis name="server" implement="MUST"/> + <response name="purge-ok"/> + <field name="ticket" domain="access ticket"> + <doc> + The access ticket must be for the access realm that holds the + queue. + </doc> + <rule implement="MUST"> + The client MUST provide a valid access ticket giving "read" access + rights to the queue's access realm. Note that purging a queue is + equivalent to reading all messages and discarding them. + </rule> + </field> + <field name = "queue" domain = "queue name"> + <doc> + Specifies the name of the queue to purge. If the queue name is + empty, refers to the current queue for the channel, which is + the last declared queue. + </doc> + <doc name = "rule"> + If the client did not previously declare a queue, and the queue + name in this method is empty, the server MUST raise a connection + exception with reply code 530 (not allowed). + </doc> + <doc name = "rule" test = "amq_queue_16"> + The queue must exist. Attempting to purge a non-existing queue + causes a channel exception. + </doc> + </field> + + <field name = "nowait" type = "bit"> + do not send a reply method + <doc> + If set, the server will not respond to the method. The client should + not wait for a reply method. If the server could not complete the + method it will raise a channel or connection exception. + </doc> + </field> + </method> + <method name="purge-ok" synchronous="1" index="31"> + confirms a queue purge + <doc> + This method confirms the purge of a queue. + </doc> + <chassis name="client" implement="MUST"/> + <field name="message count" type="long"> + number of messages purged + <doc> + Reports the number of messages purged. + </doc> + </field> + </method> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="delete" synchronous="1" index="40"> + delete a queue + <doc> + This method deletes a queue. When a queue is deleted any pending + messages are sent to a dead-letter queue if this is defined in the + server configuration, and all consumers on the queue are cancelled. + </doc> + <rule implement="SHOULD"> + <test>amq_queue_43</test> + The server SHOULD use a dead-letter queue to hold messages that + were pending on a deleted queue, and MAY provide facilities for + a system administrator to move these messages back to an active + queue. + </rule> + <chassis name="server" implement="MUST"/> + <response name="delete-ok"/> + <field name="ticket" domain="access ticket"> + <doc> + The client provides a valid access ticket giving "active" + access rights to the queue's access realm. + </doc> + </field> + + <field name = "queue" domain = "queue name"> + <doc> + Specifies the name of the queue to delete. If the queue name is + empty, refers to the current queue for the channel, which is the + last declared queue. + </doc> + <doc name = "rule"> + If the client did not previously declare a queue, and the queue + name in this method is empty, the server MUST raise a connection + exception with reply code 530 (not allowed). + </doc> + <doc name = "rule" test = "amq_queue_21"> + The queue must exist. Attempting to delete a non-existing queue + causes a channel exception. + </doc> + </field> + + <field name="if unused" type="bit"> + delete only if unused + <doc> + If set, the server will only delete the queue if it has no + consumers. If the queue has consumers the server does does not + delete it but raises a channel exception instead. + </doc> + <rule implement="MUST"> + <test>amq_queue_29</test> + <test>amq_queue_30</test> + The server MUST respect the if-unused flag when deleting a queue. + </rule> + </field> + <field name="if empty" type="bit"> + delete only if empty + <test>amq_queue_27</test> + <doc> + If set, the server will only delete the queue if it has no + messages. If the queue is not empty the server raises a channel + exception. + </doc> + </field> + <field name = "nowait" type = "bit"> + do not send a reply method + <doc> + If set, the server will not respond to the method. The client should + not wait for a reply method. If the server could not complete the + method it will raise a channel or connection exception. + </doc> + </field> + </method> + + <method name="delete-ok" synchronous="1" index="41"> + confirm deletion of a queue + <doc> + This method confirms the deletion of a queue. + </doc> + <chassis name="client" implement="MUST"/> + <field name="message count" type="long"> + number of messages purged + <doc> + Reports the number of messages purged. + </doc> + </field> + </method> + </class> + <class name="basic" handler="channel" index="60"> + <!-- +====================================================== +== BASIC MIDDLEWARE +====================================================== +--> + work with basic content +<doc> + The Basic class provides methods that support an industry-standard + messaging model. +</doc> + +<doc name = "grammar"> + basic = C:QOS S:QOS-OK + / C:CONSUME S:CONSUME-OK + / C:CANCEL S:CANCEL-OK + / C:PUBLISH content + / S:RETURN content + / S:DELIVER content + / C:GET ( S:GET-OK content / S:GET-EMPTY ) + / C:ACK + / C:REJECT +</doc> + +<chassis name = "server" implement = "MUST" /> +<chassis name = "client" implement = "MAY" /> + +<doc name = "rule" test = "amq_basic_08"> + The server SHOULD respect the persistent property of basic messages + and SHOULD make a best-effort to hold persistent basic messages on a + reliable storage mechanism. +</doc> +<doc name = "rule" test = "amq_basic_09"> + The server MUST NOT discard a persistent basic message in case of a + queue overflow. The server MAY use the Channel.Flow method to slow + or stop a basic message publisher when necessary. +</doc> +<doc name = "rule" test = "amq_basic_10"> + The server MAY overflow non-persistent basic messages to persistent + storage and MAY discard or dead-letter non-persistent basic messages + on a priority basis if the queue size exceeds some configured limit. +</doc> +<doc name = "rule" test = "amq_basic_11"> + The server MUST implement at least 2 priority levels for basic + messages, where priorities 0-4 and 5-9 are treated as two distinct + levels. The server MAY implement up to 10 priority levels. +</doc> +<doc name = "rule" test = "amq_basic_12"> + The server MUST deliver messages of the same priority in order + irrespective of their individual persistence. +</doc> +<doc name = "rule" test = "amq_basic_13"> + The server MUST support both automatic and explicit acknowledgements + on Basic content. +</doc> + +<!-- These are the properties for a Basic content --> + +<field name = "content type" type = "shortstr"> + MIME content type +</field> +<field name = "content encoding" type = "shortstr"> + MIME content encoding +</field> +<field name = "headers" type = "table"> + Message header field table +</field> +<field name = "delivery mode" type = "octet"> + Non-persistent (1) or persistent (2) +</field> +<field name = "priority" type = "octet"> + The message priority, 0 to 9 +</field> +<field name = "correlation id" type = "shortstr"> + The application correlation identifier +</field> +<field name = "reply to" type = "shortstr"> + The destination to reply to +</field> +<field name = "expiration" type = "shortstr"> + Message expiration specification +</field> +<field name = "message id" type = "shortstr"> + The application message identifier +</field> +<field name = "timestamp" type = "timestamp"> + The message timestamp +</field> +<field name = "type" type = "shortstr"> + The message type name +</field> +<field name = "user id" type = "shortstr"> + The creating user id +</field> +<field name = "app id" type = "shortstr"> + The creating application id +</field> +<field name = "cluster id" type = "shortstr"> + Intra-cluster routing identifier +</field> + + +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + +<method name = "qos" synchronous = "1" index = "10"> + specify quality of service + <doc> + This method requests a specific quality of service. The QoS can + be specified for the current channel or for all channels on the + connection. The particular properties and semantics of a qos method + always depend on the content class semantics. Though the qos method + could in principle apply to both peers, it is currently meaningful + only for the server. + </doc> + <chassis name = "server" implement = "MUST" /> + <response name = "qos-ok" /> + + <field name = "prefetch size" type = "long"> + prefetch window in octets + <doc> + The client can request that messages be sent in advance so that + when the client finishes processing a message, the following + message is already held locally, rather than needing to be sent + down the channel. Prefetching gives a performance improvement. + This field specifies the prefetch window size in octets. The + server will send a message in advance if it is equal to or + smaller in size than the available prefetch size (and also falls + into other prefetch limits). May be set to zero, meaning "no + specific limit", although other prefetch limits may still apply. + The prefetch-size is ignored if the no-ack option is set. + </doc> + <doc name = "rule" test = "amq_basic_17"> + The server MUST ignore this setting when the client is not + processing any messages - i.e. the prefetch size does not limit + the transfer of single messages to a client, only the sending in + advance of more messages while the client still has one or more + unacknowledged messages. + </doc> + </field> + + <field name = "prefetch count" type = "short"> + prefetch window in messages + <doc> + Specifies a prefetch window in terms of whole messages. This + field may be used in combination with the prefetch-size field; + a message will only be sent in advance if both prefetch windows + (and those at the channel and connection level) allow it. + The prefetch-count is ignored if the no-ack option is set. + </doc> + <doc name = "rule" test = "amq_basic_18"> + The server MAY send less data in advance than allowed by the + client's specified prefetch windows but it MUST NOT send more. + </doc> + </field> + + <field name = "global" type = "bit"> + apply to entire connection + <doc> + By default the QoS settings apply to the current channel only. If + this field is set, they are applied to the entire connection. + </doc> + </field> +</method> + +<method name = "qos-ok" synchronous = "1" index = "11"> + confirm the requested qos + <doc> + This method tells the client that the requested QoS levels could + be handled by the server. The requested QoS applies to all active + consumers until a new QoS is defined. + </doc> + <chassis name = "client" implement = "MUST" /> +</method> + +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + +<method name = "consume" synchronous = "1" index = "20"> + start a queue consumer + <doc> + This method asks the server to start a "consumer", which is a + transient request for messages from a specific queue. Consumers + last as long as the channel they were created on, or until the + client cancels them. + </doc> + <doc name = "rule" test = "amq_basic_01"> + The server SHOULD support at least 16 consumers per queue, unless + the queue was declared as private, and ideally, impose no limit + except as defined by available resources. + </doc> + <chassis name = "server" implement = "MUST" /> + <response name = "consume-ok" /> + + <field name = "ticket" domain = "access ticket"> + <doc name = "rule"> + The client MUST provide a valid access ticket giving "read" access + rights to the realm for the queue. + </doc> + </field> + + <field name = "queue" domain = "queue name"> + <doc> + Specifies the name of the queue to consume from. If the queue name + is null, refers to the current queue for the channel, which is the + last declared queue. + </doc> + <doc name = "rule"> + If the client did not previously declare a queue, and the queue name + in this method is empty, the server MUST raise a connection exception + with reply code 530 (not allowed). + </doc> + </field> + + <field name = "consumer tag" domain = "consumer tag"> + <doc> + Specifies the identifier for the consumer. The consumer tag is + local to a connection, so two clients can use the same consumer + tags. If this field is empty the server will generate a unique + tag. + </doc> + <doc name = "rule" test = "todo"> + The tag MUST NOT refer to an existing consumer. If the client + attempts to create two consumers with the same non-empty tag + the server MUST raise a connection exception with reply code + 530 (not allowed). + </doc> + </field> + + <field name = "no local" domain = "no local" /> + + <field name = "no ack" domain = "no ack" /> + + <field name = "exclusive" type = "bit"> + request exclusive access + <doc> + Request exclusive consumer access, meaning only this consumer can + access the queue. + </doc> + <doc name = "rule" test = "amq_basic_02"> + If the server cannot grant exclusive access to the queue when asked, + - because there are other consumers active - it MUST raise a channel + exception with return code 403 (access refused). + </doc> + </field> + + <field name = "nowait" type = "bit"> + do not send a reply method + <doc> + If set, the server will not respond to the method. The client should + not wait for a reply method. If the server could not complete the + method it will raise a channel or connection exception. + </doc> + </field> +</method> + +<method name = "consume-ok" synchronous = "1" index = "21"> + confirm a new consumer + <doc> + The server provides the client with a consumer tag, which is used + by the client for methods called on the consumer at a later stage. + </doc> + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer tag" domain = "consumer tag"> + <doc> + Holds the consumer tag specified by the client or provided by + the server. + </doc> + </field> +</method> + + +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + +<method name = "cancel" synchronous = "1" index = "30"> + end a queue consumer + <doc test = "amq_basic_04"> + This method cancels a consumer. This does not affect already + delivered messages, but it does mean the server will not send any + more messages for that consumer. The client may receive an + abitrary number of messages in between sending the cancel method + and receiving the cancel-ok reply. + </doc> + <doc name = "rule" test = "todo"> + If the queue no longer exists when the client sends a cancel command, + or the consumer has been cancelled for other reasons, this command + has no effect. + </doc> + <chassis name = "server" implement = "MUST" /> + <response name = "cancel-ok" /> + + <field name = "consumer tag" domain = "consumer tag" /> + + <field name = "nowait" type = "bit"> + do not send a reply method + <doc> + If set, the server will not respond to the method. The client should + not wait for a reply method. If the server could not complete the + method it will raise a channel or connection exception. + </doc> + </field> +</method> + +<method name = "cancel-ok" synchronous = "1" index = "31"> + confirm a cancelled consumer + <doc> + This method confirms that the cancellation was completed. + </doc> + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer tag" domain = "consumer tag" /> +</method> + + +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + +<method name = "publish" content = "1" index = "40"> + publish a message + <doc> + This method publishes a message to a specific exchange. The message + will be routed to queues as defined by the exchange configuration + and distributed to any active consumers when the transaction, if any, + is committed. + </doc> + <chassis name = "server" implement = "MUST" /> + + <field name = "ticket" domain = "access ticket"> + <doc name = "rule"> + The client MUST provide a valid access ticket giving "write" + access rights to the access realm for the exchange. + </doc> + </field> + + <field name = "exchange" domain = "exchange name"> + <doc> + Specifies the name of the exchange to publish to. The exchange + name can be empty, meaning the default exchange. If the exchange + name is specified, and that exchange does not exist, the server + will raise a channel exception. + </doc> + <doc name = "rule" test = "amq_basic_06"> + The server MUST accept a blank exchange name to mean the default + exchange. + </doc> + <doc name = "rule" test = "amq_basic_14"> + If the exchange was declared as an internal exchange, the server + MUST raise a channel exception with a reply code 403 (access + refused). + </doc> + <doc name = "rule" test = "amq_basic_15"> + The exchange MAY refuse basic content in which case it MUST raise + a channel exception with reply code 540 (not implemented). + </doc> + </field> + + <field name = "routing key" type = "shortstr"> + Message routing key + <doc> + Specifies the routing key for the message. The routing key is + used for routing messages depending on the exchange configuration. + </doc> + </field> + + <field name = "mandatory" type = "bit"> + indicate mandatory routing + <doc> + This flag tells the server how to react if the message cannot be + routed to a queue. If this flag is set, the server will return an + unroutable message with a Return method. If this flag is zero, the + server silently drops the message. + </doc> + <doc name = "rule" test = "amq_basic_07"> + The server SHOULD implement the mandatory flag. + </doc> + </field> + + <field name = "immediate" type = "bit"> + request immediate delivery + <doc> + This flag tells the server how to react if the message cannot be + routed to a queue consumer immediately. If this flag is set, the + server will return an undeliverable message with a Return method. + If this flag is zero, the server will queue the message, but with + no guarantee that it will ever be consumed. + </doc> + <doc name = "rule" test = "amq_basic_16"> + The server SHOULD implement the immediate flag. + </doc> + </field> +</method> + +<method name = "return" content = "1" index = "50"> + return a failed message + <doc> + This method returns an undeliverable message that was published + with the "immediate" flag set, or an unroutable message published + with the "mandatory" flag set. The reply code and text provide + information about the reason that the message was undeliverable. + </doc> + <chassis name = "client" implement = "MUST" /> + + <field name = "reply code" domain = "reply code" /> + <field name = "reply text" domain = "reply text" /> + + <field name = "exchange" domain = "exchange name"> + <doc> + Specifies the name of the exchange that the message was + originally published to. + </doc> + </field> + + <field name = "routing key" type = "shortstr"> + Message routing key + <doc> + Specifies the routing key name specified when the message was + published. + </doc> + </field> +</method> + + +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + +<method name = "deliver" content = "1" index = "60"> + notify the client of a consumer message + <doc> + This method delivers a message to the client, via a consumer. In + the asynchronous message delivery model, the client starts a + consumer using the Consume method, then the server responds with + Deliver methods as and when messages arrive for that consumer. + </doc> + <doc name = "rule" test = "amq_basic_19"> + The server SHOULD track the number of times a message has been + delivered to clients and when a message is redelivered a certain + number of times - e.g. 5 times - without being acknowledged, the + server SHOULD consider the message to be unprocessable (possibly + causing client applications to abort), and move the message to a + dead letter queue. + </doc> + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer tag" domain = "consumer tag" /> + + <field name = "delivery tag" domain = "delivery tag" /> + + <field name = "redelivered" domain = "redelivered" /> + + <field name = "exchange" domain = "exchange name"> + <doc> + Specifies the name of the exchange that the message was + originally published to. + </doc> + </field> + + <field name = "routing key" type = "shortstr"> + Message routing key + <doc> + Specifies the routing key name specified when the message was + published. + </doc> + </field> +</method> + + +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + +<method name = "get" synchronous = "1" index = "70"> + direct access to a queue + <doc> + This method provides a direct access to the messages in a queue + using a synchronous dialogue that is designed for specific types of + application where synchronous functionality is more important than + performance. + </doc> + <response name = "get-ok" /> + <response name = "get-empty" /> + <chassis name = "server" implement = "MUST" /> + + <field name = "ticket" domain = "access ticket"> + <doc name = "rule"> + The client MUST provide a valid access ticket giving "read" + access rights to the realm for the queue. + </doc> + </field> + + <field name = "queue" domain = "queue name"> + <doc> + Specifies the name of the queue to consume from. If the queue name + is null, refers to the current queue for the channel, which is the + last declared queue. + </doc> + <doc name = "rule"> + If the client did not previously declare a queue, and the queue name + in this method is empty, the server MUST raise a connection exception + with reply code 530 (not allowed). + </doc> + </field> + + <field name = "no ack" domain = "no ack" /> +</method> + +<method name = "get-ok" synchronous = "1" content = "1" index = "71"> + provide client with a message + <doc> + This method delivers a message to the client following a get + method. A message delivered by 'get-ok' must be acknowledged + unless the no-ack option was set in the get method. + </doc> + <chassis name = "client" implement = "MAY" /> + + <field name = "delivery tag" domain = "delivery tag" /> + + <field name = "redelivered" domain = "redelivered" /> + + <field name = "exchange" domain = "exchange name"> + <doc> + Specifies the name of the exchange that the message was originally + published to. If empty, the message was published to the default + exchange. + </doc> + </field> + + <field name = "routing key" type = "shortstr"> + Message routing key + <doc> + Specifies the routing key name specified when the message was + published. + </doc> + </field> + + <field name = "message count" type = "long" > + number of messages pending + <doc> + This field reports the number of messages pending on the queue, + excluding the message being delivered. Note that this figure is + indicative, not reliable, and can change arbitrarily as messages + are added to the queue and removed by other clients. + </doc> + </field> +</method> + + +<method name = "get-empty" synchronous = "1" index = "72"> + indicate no messages available + <doc> + This method tells the client that the queue has no messages + available for the client. + </doc> + <chassis name = "client" implement = "MAY" /> + + <field name = "cluster id" type = "shortstr"> + Cluster id + <doc> + For use by cluster applications, should not be used by + client applications. + </doc> + </field> +</method> + +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + +<method name = "ack" index = "80"> + acknowledge one or more messages + <doc> + This method acknowledges one or more messages delivered via the + Deliver or Get-Ok methods. The client can ask to confirm a + single message or a set of messages up to and including a specific + message. + </doc> + <chassis name = "server" implement = "MUST" /> + <field name = "delivery tag" domain = "delivery tag" /> + + <field name = "multiple" type = "bit"> + acknowledge multiple messages + <doc> + If set to 1, the delivery tag is treated as "up to and including", + so that the client can acknowledge multiple messages with a single + method. If set to zero, the delivery tag refers to a single + message. If the multiple field is 1, and the delivery tag is zero, + tells the server to acknowledge all outstanding mesages. + </doc> + <doc name = "rule" test = "amq_basic_20"> + The server MUST validate that a non-zero delivery-tag refers to an + delivered message, and raise a channel exception if this is not the + case. + </doc> + </field> +</method> + +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + +<method name = "reject" index = "90"> + reject an incoming message + <doc> + This method allows a client to reject a message. It can be used to + interrupt and cancel large incoming messages, or return untreatable + messages to their original queue. + </doc> + <doc name = "rule" test = "amq_basic_21"> + The server SHOULD be capable of accepting and process the Reject + method while sending message content with a Deliver or Get-Ok + method. I.e. the server should read and process incoming methods + while sending output frames. To cancel a partially-send content, + the server sends a content body frame of size 1 (i.e. with no data + except the frame-end octet). + </doc> + <doc name = "rule" test = "amq_basic_22"> + The server SHOULD interpret this method as meaning that the client + is unable to process the message at this time. + </doc> + <doc name = "rule"> + A client MUST NOT use this method as a means of selecting messages + to process. A rejected message MAY be discarded or dead-lettered, + not necessarily passed to another client. + </doc> + <chassis name = "server" implement = "MUST" /> + + <field name = "delivery tag" domain = "delivery tag" /> + + <field name = "requeue" type = "bit"> + requeue the message + <doc> + If this field is zero, the message will be discarded. If this bit + is 1, the server will attempt to requeue the message. + </doc> + <doc name = "rule" test = "amq_basic_23"> + The server MUST NOT deliver the message to the same client within + the context of the current channel. The recommended strategy is + to attempt to deliver the message to an alternative consumer, and + if that is not possible, to move the message to a dead-letter + queue. The server MAY use more sophisticated tracking to hold + the message on the queue and redeliver it to the same client at + a later stage. + </doc> + </field> +</method> + +<method name = "recover" index = "100"> + redeliver unacknowledged messages + <doc> + This method asks the broker to redeliver all unacknowledged messages on a + specified channel. Zero or more messages may be redelivered. This method + is only allowed on non-transacted channels. + </doc> + <chassis name = "server" implement = "MUST" /> + + <field name = "requeue" type = "bit"> + requeue the message + <doc> + If this field is zero, the message will be redelivered to the original + recipient. If this bit is 1, the server will attempt to requeue the + message, potentially then delivering it to an alternative subscriber. + </doc> + </field> + <doc name="rule"> + The server MUST set the redelivered flag on all messages that are resent. + </doc> + <doc name="rule"> + The server MUST raise a channel exception if this is called on a + transacted channel. + </doc> +</method> + +</class> + + + <class name="file" handler="channel" index="70"> + <!-- +====================================================== +== FILE TRANSFER +====================================================== +--> + work with file content +<doc> + The file class provides methods that support reliable file transfer. + File messages have a specific set of properties that are required for + interoperability with file transfer applications. File messages and + acknowledgements are subject to channel transactions. Note that the + file class does not provide message browsing methods; these are not + compatible with the staging model. Applications that need browsable + file transfer should use Basic content and the Basic class. +</doc> + +<doc name = "grammar"> + file = C:QOS S:QOS-OK + / C:CONSUME S:CONSUME-OK + / C:CANCEL S:CANCEL-OK + / C:OPEN S:OPEN-OK C:STAGE content + / S:OPEN C:OPEN-OK S:STAGE content + / C:PUBLISH + / S:DELIVER + / S:RETURN + / C:ACK + / C:REJECT +</doc> + +<chassis name = "server" implement = "MAY" /> +<chassis name = "client" implement = "MAY" /> + +<doc name = "rule"> + The server MUST make a best-effort to hold file messages on a + reliable storage mechanism. +</doc> +<doc name = "rule"> + The server MUST NOT discard a file message in case of a queue + overflow. The server MUST use the Channel.Flow method to slow or stop + a file message publisher when necessary. +</doc> +<doc name = "rule"> + The server MUST implement at least 2 priority levels for file + messages, where priorities 0-4 and 5-9 are treated as two distinct + levels. The server MAY implement up to 10 priority levels. +</doc> +<doc name = "rule"> + The server MUST support both automatic and explicit acknowledgements + on file content. +</doc> + +<!-- These are the properties for a File content --> + +<field name = "content type" type = "shortstr"> + MIME content type +</field> +<field name = "content encoding" type = "shortstr"> + MIME content encoding +</field> +<field name = "headers" type = "table"> + Message header field table +</field> +<field name = "priority" type = "octet"> + The message priority, 0 to 9 +</field> +<field name = "reply to" type = "shortstr"> + The destination to reply to +</field> +<field name = "message id" type = "shortstr"> + The application message identifier +</field> +<field name = "filename" type = "shortstr"> + The message filename +</field> +<field name = "timestamp" type = "timestamp"> + The message timestamp +</field> +<field name = "cluster id" type = "shortstr"> + Intra-cluster routing identifier +</field> + + +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + +<method name = "qos" synchronous = "1" index = "10"> + specify quality of service + <doc> + This method requests a specific quality of service. The QoS can + be specified for the current channel or for all channels on the + connection. The particular properties and semantics of a qos method + always depend on the content class semantics. Though the qos method + could in principle apply to both peers, it is currently meaningful + only for the server. + </doc> + <chassis name = "server" implement = "MUST" /> + <response name = "qos-ok" /> + + <field name = "prefetch size" type = "long"> + prefetch window in octets + <doc> + The client can request that messages be sent in advance so that + when the client finishes processing a message, the following + message is already held locally, rather than needing to be sent + down the channel. Prefetching gives a performance improvement. + This field specifies the prefetch window size in octets. May be + set to zero, meaning "no specific limit". Note that other + prefetch limits may still apply. The prefetch-size is ignored + if the no-ack option is set. + </doc> + </field> + + <field name = "prefetch count" type = "short"> + prefetch window in messages + <doc> + Specifies a prefetch window in terms of whole messages. This + is compatible with some file API implementations. This field + may be used in combination with the prefetch-size field; a + message will only be sent in advance if both prefetch windows + (and those at the channel and connection level) allow it. + The prefetch-count is ignored if the no-ack option is set. + </doc> + <doc name = "rule"> + The server MAY send less data in advance than allowed by the + client's specified prefetch windows but it MUST NOT send more. + </doc> + </field> + + <field name = "global" type = "bit"> + apply to entire connection + <doc> + By default the QoS settings apply to the current channel only. If + this field is set, they are applied to the entire connection. + </doc> + </field> +</method> + +<method name = "qos-ok" synchronous = "1" index = "11"> + confirm the requested qos + <doc> + This method tells the client that the requested QoS levels could + be handled by the server. The requested QoS applies to all active + consumers until a new QoS is defined. + </doc> + <chassis name = "client" implement = "MUST" /> +</method> + +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + +<method name = "consume" synchronous = "1" index = "20"> + start a queue consumer + <doc> + This method asks the server to start a "consumer", which is a + transient request for messages from a specific queue. Consumers + last as long as the channel they were created on, or until the + client cancels them. + </doc> + <doc name = "rule"> + The server SHOULD support at least 16 consumers per queue, unless + the queue was declared as private, and ideally, impose no limit + except as defined by available resources. + </doc> + <chassis name = "server" implement = "MUST" /> + <response name = "consume-ok" /> + + <field name = "ticket" domain = "access ticket"> + <doc name = "rule"> + The client MUST provide a valid access ticket giving "read" access + rights to the realm for the queue. + </doc> + </field> + + <field name = "queue" domain = "queue name"> + <doc> + Specifies the name of the queue to consume from. If the queue name + is null, refers to the current queue for the channel, which is the + last declared queue. + </doc> + <doc name = "rule"> + If the client did not previously declare a queue, and the queue name + in this method is empty, the server MUST raise a connection exception + with reply code 530 (not allowed). + </doc> + </field> + + <field name = "consumer tag" domain = "consumer tag"> + <doc> + Specifies the identifier for the consumer. The consumer tag is + local to a connection, so two clients can use the same consumer + tags. If this field is empty the server will generate a unique + tag. + </doc> + <doc name = "rule" test = "todo"> + The tag MUST NOT refer to an existing consumer. If the client + attempts to create two consumers with the same non-empty tag + the server MUST raise a connection exception with reply code + 530 (not allowed). + </doc> + </field> + + <field name = "no local" domain = "no local" /> + + <field name = "no ack" domain = "no ack" /> + + <field name = "exclusive" type = "bit"> + request exclusive access + <doc> + Request exclusive consumer access, meaning only this consumer can + access the queue. + </doc> + <doc name = "rule" test = "amq_file_00"> + If the server cannot grant exclusive access to the queue when asked, + - because there are other consumers active - it MUST raise a channel + exception with return code 405 (resource locked). + </doc> + </field> + + <field name = "nowait" type = "bit"> + do not send a reply method + <doc> + If set, the server will not respond to the method. The client should + not wait for a reply method. If the server could not complete the + method it will raise a channel or connection exception. + </doc> + </field> +</method> + +<method name = "consume-ok" synchronous = "1" index = "21"> + confirm a new consumer + <doc> + This method provides the client with a consumer tag which it MUST + use in methods that work with the consumer. + </doc> + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer tag" domain = "consumer tag"> + <doc> + Holds the consumer tag specified by the client or provided by + the server. + </doc> + </field> +</method> + + +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + +<method name = "cancel" synchronous = "1" index = "30"> + end a queue consumer + <doc> + This method cancels a consumer. This does not affect already + delivered messages, but it does mean the server will not send any + more messages for that consumer. + </doc> + <chassis name = "server" implement = "MUST" /> + <response name = "cancel-ok" /> + + <field name = "consumer tag" domain = "consumer tag" /> + + <field name = "nowait" type = "bit"> + do not send a reply method + <doc> + If set, the server will not respond to the method. The client should + not wait for a reply method. If the server could not complete the + method it will raise a channel or connection exception. + </doc> + </field> +</method> + +<method name = "cancel-ok" synchronous = "1" index = "31"> + confirm a cancelled consumer + <doc> + This method confirms that the cancellation was completed. + </doc> + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer tag" domain = "consumer tag" /> +</method> + + +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + +<method name = "open" synchronous = "1" index = "40"> + request to start staging + <doc> + This method requests permission to start staging a message. Staging + means sending the message into a temporary area at the recipient end + and then delivering the message by referring to this temporary area. + Staging is how the protocol handles partial file transfers - if a + message is partially staged and the connection breaks, the next time + the sender starts to stage it, it can restart from where it left off. + </doc> + <response name = "open-ok" /> + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> + + <field name = "identifier" type = "shortstr"> + staging identifier + <doc> + This is the staging identifier. This is an arbitrary string chosen + by the sender. For staging to work correctly the sender must use + the same staging identifier when staging the same message a second + time after recovery from a failure. A good choice for the staging + identifier would be the SHA1 hash of the message properties data + (including the original filename, revised time, etc.). + </doc> + </field> + + <field name = "content size" type = "longlong"> + message content size + <doc> + The size of the content in octets. The recipient may use this + information to allocate or check available space in advance, to + avoid "disk full" errors during staging of very large messages. + </doc> + <doc name = "rule"> + The sender MUST accurately fill the content-size field. + Zero-length content is permitted. + </doc> + </field> +</method> + +<method name = "open-ok" synchronous = "1" index = "41"> + confirm staging ready + <doc> + This method confirms that the recipient is ready to accept staged + data. If the message was already partially-staged at a previous + time the recipient will report the number of octets already staged. + </doc> + <response name = "stage" /> + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> + + <field name = "staged size" type = "longlong"> + already staged amount + <doc> + The amount of previously-staged content in octets. For a new + message this will be zero. + </doc> + <doc name = "rule"> + The sender MUST start sending data from this octet offset in the + message, counting from zero. + </doc> + <doc name = "rule"> + The recipient MAY decide how long to hold partially-staged content + and MAY implement staging by always discarding partially-staged + content. However if it uses the file content type it MUST support + the staging methods. + </doc> + </field> +</method> + +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + +<method name = "stage" content = "1" index = "50"> + stage message content + <doc> + This method stages the message, sending the message content to the + recipient from the octet offset specified in the Open-Ok method. + </doc> + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> +</method> + + +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + +<method name = "publish" index = "60"> + publish a message + <doc> + This method publishes a staged file message to a specific exchange. + The file message will be routed to queues as defined by the exchange + configuration and distributed to any active consumers when the + transaction, if any, is committed. + </doc> + <chassis name = "server" implement = "MUST" /> + + <field name = "ticket" domain = "access ticket"> + <doc name = "rule"> + The client MUST provide a valid access ticket giving "write" + access rights to the access realm for the exchange. + </doc> + </field> + + <field name = "exchange" domain = "exchange name"> + <doc> + Specifies the name of the exchange to publish to. The exchange + name can be empty, meaning the default exchange. If the exchange + name is specified, and that exchange does not exist, the server + will raise a channel exception. + </doc> + <doc name = "rule"> + The server MUST accept a blank exchange name to mean the default + exchange. + </doc> + <doc name = "rule"> + If the exchange was declared as an internal exchange, the server + MUST respond with a reply code 403 (access refused) and raise a + channel exception. + </doc> + <doc name = "rule"> + The exchange MAY refuse file content in which case it MUST respond + with a reply code 540 (not implemented) and raise a channel + exception. + </doc> + </field> + + <field name = "routing key" type = "shortstr"> + Message routing key + <doc> + Specifies the routing key for the message. The routing key is + used for routing messages depending on the exchange configuration. + </doc> + </field> + + <field name = "mandatory" type = "bit"> + indicate mandatory routing + <doc> + This flag tells the server how to react if the message cannot be + routed to a queue. If this flag is set, the server will return an + unroutable message with a Return method. If this flag is zero, the + server silently drops the message. + </doc> + <doc name = "rule" test = "amq_file_00"> + The server SHOULD implement the mandatory flag. + </doc> + </field> + + <field name = "immediate" type = "bit"> + request immediate delivery + <doc> + This flag tells the server how to react if the message cannot be + routed to a queue consumer immediately. If this flag is set, the + server will return an undeliverable message with a Return method. + If this flag is zero, the server will queue the message, but with + no guarantee that it will ever be consumed. + </doc> + <doc name = "rule" test = "amq_file_00"> + The server SHOULD implement the immediate flag. + </doc> + </field> + + <field name = "identifier" type = "shortstr"> + staging identifier + <doc> + This is the staging identifier of the message to publish. The + message must have been staged. Note that a client can send the + Publish method asynchronously without waiting for staging to + finish. + </doc> + </field> +</method> + +<method name = "return" content = "1" index = "70"> + return a failed message + <doc> + This method returns an undeliverable message that was published + with the "immediate" flag set, or an unroutable message published + with the "mandatory" flag set. The reply code and text provide + information about the reason that the message was undeliverable. + </doc> + <chassis name = "client" implement = "MUST" /> + + <field name = "reply code" domain = "reply code" /> + <field name = "reply text" domain = "reply text" /> + + <field name = "exchange" domain = "exchange name"> + <doc> + Specifies the name of the exchange that the message was + originally published to. + </doc> + </field> + + <field name = "routing key" type = "shortstr"> + Message routing key + <doc> + Specifies the routing key name specified when the message was + published. + </doc> + </field> +</method> + + +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + +<method name = "deliver" index = "80"> + notify the client of a consumer message + <doc> + This method delivers a staged file message to the client, via a + consumer. In the asynchronous message delivery model, the client + starts a consumer using the Consume method, then the server + responds with Deliver methods as and when messages arrive for + that consumer. + </doc> + <doc name = "rule"> + The server SHOULD track the number of times a message has been + delivered to clients and when a message is redelivered a certain + number of times - e.g. 5 times - without being acknowledged, the + server SHOULD consider the message to be unprocessable (possibly + causing client applications to abort), and move the message to a + dead letter queue. + </doc> + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer tag" domain = "consumer tag" /> + + <field name = "delivery tag" domain = "delivery tag" /> + + <field name = "redelivered" domain = "redelivered" /> + + <field name = "exchange" domain = "exchange name"> + <doc> + Specifies the name of the exchange that the message was originally + published to. + </doc> + </field> + + <field name = "routing key" type = "shortstr"> + Message routing key + <doc> + Specifies the routing key name specified when the message was + published. + </doc> + </field> + + <field name = "identifier" type = "shortstr"> + staging identifier + <doc> + This is the staging identifier of the message to deliver. The + message must have been staged. Note that a server can send the + Deliver method asynchronously without waiting for staging to + finish. + </doc> + </field> +</method> + + +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + +<method name = "ack" index = "90"> + acknowledge one or more messages + <doc> + This method acknowledges one or more messages delivered via the + Deliver method. The client can ask to confirm a single message or + a set of messages up to and including a specific message. + </doc> + <chassis name = "server" implement = "MUST" /> + <field name = "delivery tag" domain = "delivery tag" /> + + <field name = "multiple" type = "bit"> + acknowledge multiple messages + <doc> + If set to 1, the delivery tag is treated as "up to and including", + so that the client can acknowledge multiple messages with a single + method. If set to zero, the delivery tag refers to a single + message. If the multiple field is 1, and the delivery tag is zero, + tells the server to acknowledge all outstanding mesages. + </doc> + <doc name = "rule"> + The server MUST validate that a non-zero delivery-tag refers to an + delivered message, and raise a channel exception if this is not the + case. + </doc> + </field> +</method> + + +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + +<method name = "reject" index = "100"> + reject an incoming message + <doc> + This method allows a client to reject a message. It can be used to + return untreatable messages to their original queue. Note that file + content is staged before delivery, so the client will not use this + method to interrupt delivery of a large message. + </doc> + <doc name = "rule"> + The server SHOULD interpret this method as meaning that the client + is unable to process the message at this time. + </doc> + <doc name = "rule"> + A client MUST NOT use this method as a means of selecting messages + to process. A rejected message MAY be discarded or dead-lettered, + not necessarily passed to another client. + </doc> + <chassis name = "server" implement = "MUST" /> + + <field name = "delivery tag" domain = "delivery tag" /> + + <field name = "requeue" type = "bit"> + requeue the message + <doc> + If this field is zero, the message will be discarded. If this bit + is 1, the server will attempt to requeue the message. + </doc> + <doc name = "rule"> + The server MUST NOT deliver the message to the same client within + the context of the current channel. The recommended strategy is + to attempt to deliver the message to an alternative consumer, and + if that is not possible, to move the message to a dead-letter + queue. The server MAY use more sophisticated tracking to hold + the message on the queue and redeliver it to the same client at + a later stage. + </doc> + </field> +</method> + +</class> + + <class name="stream" handler="channel" index="80"> + <!-- +====================================================== +== STREAMING +====================================================== +--> + work with streaming content + +<doc> + The stream class provides methods that support multimedia streaming. + The stream class uses the following semantics: one message is one + packet of data; delivery is unacknowleged and unreliable; the consumer + can specify quality of service parameters that the server can try to + adhere to; lower-priority messages may be discarded in favour of high + priority messages. +</doc> + +<doc name = "grammar"> + stream = C:QOS S:QOS-OK + / C:CONSUME S:CONSUME-OK + / C:CANCEL S:CANCEL-OK + / C:PUBLISH content + / S:RETURN + / S:DELIVER content +</doc> + +<chassis name = "server" implement = "MAY" /> +<chassis name = "client" implement = "MAY" /> + +<doc name = "rule"> + The server SHOULD discard stream messages on a priority basis if + the queue size exceeds some configured limit. +</doc> +<doc name = "rule"> + The server MUST implement at least 2 priority levels for stream + messages, where priorities 0-4 and 5-9 are treated as two distinct + levels. The server MAY implement up to 10 priority levels. +</doc> +<doc name = "rule"> + The server MUST implement automatic acknowledgements on stream + content. That is, as soon as a message is delivered to a client + via a Deliver method, the server must remove it from the queue. +</doc> + + +<!-- These are the properties for a Stream content --> + +<field name = "content type" type = "shortstr"> + MIME content type +</field> +<field name = "content encoding" type = "shortstr"> + MIME content encoding +</field> +<field name = "headers" type = "table"> + Message header field table +</field> +<field name = "priority" type = "octet"> + The message priority, 0 to 9 +</field> +<field name = "timestamp" type = "timestamp"> + The message timestamp +</field> + + +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + +<method name = "qos" synchronous = "1" index = "10"> + specify quality of service + <doc> + This method requests a specific quality of service. The QoS can + be specified for the current channel or for all channels on the + connection. The particular properties and semantics of a qos method + always depend on the content class semantics. Though the qos method + could in principle apply to both peers, it is currently meaningful + only for the server. + </doc> + <chassis name = "server" implement = "MUST" /> + <response name = "qos-ok" /> + + <field name = "prefetch size" type = "long"> + prefetch window in octets + <doc> + The client can request that messages be sent in advance so that + when the client finishes processing a message, the following + message is already held locally, rather than needing to be sent + down the channel. Prefetching gives a performance improvement. + This field specifies the prefetch window size in octets. May be + set to zero, meaning "no specific limit". Note that other + prefetch limits may still apply. + </doc> + </field> + + <field name = "prefetch count" type = "short"> + prefetch window in messages + <doc> + Specifies a prefetch window in terms of whole messages. This + field may be used in combination with the prefetch-size field; + a message will only be sent in advance if both prefetch windows + (and those at the channel and connection level) allow it. + </doc> + </field> + + <field name = "consume rate" type = "long"> + transfer rate in octets/second + <doc> + Specifies a desired transfer rate in octets per second. This is + usually determined by the application that uses the streaming + data. A value of zero means "no limit", i.e. as rapidly as + possible. + </doc> + <doc name = "rule"> + The server MAY ignore the prefetch values and consume rates, + depending on the type of stream and the ability of the server + to queue and/or reply it. The server MAY drop low-priority + messages in favour of high-priority messages. + </doc> + </field> + + <field name = "global" type = "bit"> + apply to entire connection + <doc> + By default the QoS settings apply to the current channel only. If + this field is set, they are applied to the entire connection. + </doc> + </field> +</method> + +<method name = "qos-ok" synchronous = "1" index = "11"> + confirm the requested qos + <doc> + This method tells the client that the requested QoS levels could + be handled by the server. The requested QoS applies to all active + consumers until a new QoS is defined. + </doc> + <chassis name = "client" implement = "MUST" /> +</method> + +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + +<method name = "consume" synchronous = "1" index = "20"> + start a queue consumer + <doc> + This method asks the server to start a "consumer", which is a + transient request for messages from a specific queue. Consumers + last as long as the channel they were created on, or until the + client cancels them. + </doc> + <doc name = "rule"> + The server SHOULD support at least 16 consumers per queue, unless + the queue was declared as private, and ideally, impose no limit + except as defined by available resources. + </doc> + <doc name = "rule"> + Streaming applications SHOULD use different channels to select + different streaming resolutions. AMQP makes no provision for + filtering and/or transforming streams except on the basis of + priority-based selective delivery of individual messages. + </doc> + <chassis name = "server" implement = "MUST" /> + <response name = "consume-ok" /> + + <field name = "ticket" domain = "access ticket"> + <doc name = "rule"> + The client MUST provide a valid access ticket giving "read" access + rights to the realm for the queue. + </doc> + </field> + + <field name = "queue" domain = "queue name"> + <doc> + Specifies the name of the queue to consume from. If the queue name + is null, refers to the current queue for the channel, which is the + last declared queue. + </doc> + <doc name = "rule"> + If the client did not previously declare a queue, and the queue name + in this method is empty, the server MUST raise a connection exception + with reply code 530 (not allowed). + </doc> + </field> + + <field name = "consumer tag" domain = "consumer tag"> + <doc> + Specifies the identifier for the consumer. The consumer tag is + local to a connection, so two clients can use the same consumer + tags. If this field is empty the server will generate a unique + tag. + </doc> + <doc name = "rule" test = "todo"> + The tag MUST NOT refer to an existing consumer. If the client + attempts to create two consumers with the same non-empty tag + the server MUST raise a connection exception with reply code + 530 (not allowed). + </doc> + </field> + + <field name = "no local" domain = "no local" /> + + <field name = "exclusive" type = "bit"> + request exclusive access + <doc> + Request exclusive consumer access, meaning only this consumer can + access the queue. + </doc> + <doc name = "rule" test = "amq_file_00"> + If the server cannot grant exclusive access to the queue when asked, + - because there are other consumers active - it MUST raise a channel + exception with return code 405 (resource locked). + </doc> + </field> + + <field name = "nowait" type = "bit"> + do not send a reply method + <doc> + If set, the server will not respond to the method. The client should + not wait for a reply method. If the server could not complete the + method it will raise a channel or connection exception. + </doc> + </field> +</method> + + +<method name = "consume-ok" synchronous = "1" index = "21"> + confirm a new consumer + <doc> + This method provides the client with a consumer tag which it may + use in methods that work with the consumer. + </doc> + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer tag" domain = "consumer tag"> + <doc> + Holds the consumer tag specified by the client or provided by + the server. + </doc> + </field> +</method> + +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + +<method name = "cancel" synchronous = "1" index = "30"> + end a queue consumer + <doc> + This method cancels a consumer. Since message delivery is + asynchronous the client may continue to receive messages for + a short while after canceling a consumer. It may process or + discard these as appropriate. + </doc> + <chassis name = "server" implement = "MUST" /> + <response name = "cancel-ok" /> + + <field name = "consumer tag" domain = "consumer tag" /> + + <field name = "nowait" type = "bit"> + do not send a reply method + <doc> + If set, the server will not respond to the method. The client should + not wait for a reply method. If the server could not complete the + method it will raise a channel or connection exception. + </doc> + </field> +</method> + +<method name = "cancel-ok" synchronous = "1" index = "31"> + confirm a cancelled consumer + <doc> + This method confirms that the cancellation was completed. + </doc> + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer tag" domain = "consumer tag" /> +</method> + + +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + +<method name = "publish" content = "1" index = "40"> + publish a message + <doc> + This method publishes a message to a specific exchange. The message + will be routed to queues as defined by the exchange configuration + and distributed to any active consumers as appropriate. + </doc> + <chassis name = "server" implement = "MUST" /> + + <field name = "ticket" domain = "access ticket"> + <doc name = "rule"> + The client MUST provide a valid access ticket giving "write" + access rights to the access realm for the exchange. + </doc> + </field> + + <field name = "exchange" domain = "exchange name"> + <doc> + Specifies the name of the exchange to publish to. The exchange + name can be empty, meaning the default exchange. If the exchange + name is specified, and that exchange does not exist, the server + will raise a channel exception. + </doc> + <doc name = "rule"> + The server MUST accept a blank exchange name to mean the default + exchange. + </doc> + <doc name = "rule"> + If the exchange was declared as an internal exchange, the server + MUST respond with a reply code 403 (access refused) and raise a + channel exception. + </doc> + <doc name = "rule"> + The exchange MAY refuse stream content in which case it MUST + respond with a reply code 540 (not implemented) and raise a + channel exception. + </doc> + </field> + + <field name = "routing key" type = "shortstr"> + Message routing key + <doc> + Specifies the routing key for the message. The routing key is + used for routing messages depending on the exchange configuration. + </doc> + </field> + + <field name = "mandatory" type = "bit"> + indicate mandatory routing + <doc> + This flag tells the server how to react if the message cannot be + routed to a queue. If this flag is set, the server will return an + unroutable message with a Return method. If this flag is zero, the + server silently drops the message. + </doc> + <doc name = "rule" test = "amq_stream_00"> + The server SHOULD implement the mandatory flag. + </doc> + </field> + + <field name = "immediate" type = "bit"> + request immediate delivery + <doc> + This flag tells the server how to react if the message cannot be + routed to a queue consumer immediately. If this flag is set, the + server will return an undeliverable message with a Return method. + If this flag is zero, the server will queue the message, but with + no guarantee that it will ever be consumed. + </doc> + <doc name = "rule" test = "amq_stream_00"> + The server SHOULD implement the immediate flag. + </doc> + </field> +</method> + +<method name = "return" content = "1" index = "50"> + return a failed message + <doc> + This method returns an undeliverable message that was published + with the "immediate" flag set, or an unroutable message published + with the "mandatory" flag set. The reply code and text provide + information about the reason that the message was undeliverable. + </doc> + <chassis name = "client" implement = "MUST" /> + + <field name = "reply code" domain = "reply code" /> + <field name = "reply text" domain = "reply text" /> + + <field name = "exchange" domain = "exchange name"> + <doc> + Specifies the name of the exchange that the message was + originally published to. + </doc> + </field> + + <field name = "routing key" type = "shortstr"> + Message routing key + <doc> + Specifies the routing key name specified when the message was + published. + </doc> + </field> +</method> + + +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + +<method name = "deliver" content = "1" index = "60"> + notify the client of a consumer message + <doc> + This method delivers a message to the client, via a consumer. In + the asynchronous message delivery model, the client starts a + consumer using the Consume method, then the server responds with + Deliver methods as and when messages arrive for that consumer. + </doc> + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer tag" domain = "consumer tag" /> + + <field name = "delivery tag" domain = "delivery tag" /> + + <field name = "exchange" domain = "exchange name"> + <doc> + Specifies the name of the exchange that the message was originally + published to. + </doc> + </field> + + <field name = "queue" domain = "queue name"> + <doc> + Specifies the name of the queue that the message came from. Note + that a single channel can start many consumers on different + queues. + </doc> + <assert check = "notnull" /> + </field> +</method> + </class> + + <class name="tx" handler="channel" index="90"> + <!-- +====================================================== +== TRANSACTIONS +====================================================== +--> + work with standard transactions + +<doc> + Standard transactions provide so-called "1.5 phase commit". We can + ensure that work is never lost, but there is a chance of confirmations + being lost, so that messages may be resent. Applications that use + standard transactions must be able to detect and ignore duplicate + messages. +</doc> + <rule implement="SHOULD"> + An client using standard transactions SHOULD be able to track all + messages received within a reasonable period, and thus detect and + reject duplicates of the same message. It SHOULD NOT pass these to + the application layer. +</rule> + <doc name="grammar"> + tx = C:SELECT S:SELECT-OK + / C:COMMIT S:COMMIT-OK + / C:ROLLBACK S:ROLLBACK-OK +</doc> + <chassis name="server" implement="SHOULD"/> + <chassis name="client" implement="MAY"/> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="select" synchronous="1" index="10"> +select standard transaction mode + <doc> + This method sets the channel to use standard transactions. The + client must use this method at least once on a channel before + using the Commit or Rollback methods. + </doc> + <chassis name="server" implement="MUST"/> + <response name="select-ok"/> + </method> + <method name="select-ok" synchronous="1" index="11"> +confirm transaction mode + <doc> + This method confirms to the client that the channel was successfully + set to use standard transactions. + </doc> + <chassis name="client" implement="MUST"/> + </method> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="commit" synchronous="1" index="20"> +commit the current transaction + <doc> + This method commits all messages published and acknowledged in + the current transaction. A new transaction starts immediately + after a commit. + </doc> + <chassis name="server" implement="MUST"/> + <response name="commit-ok"/> + </method> + <method name="commit-ok" synchronous="1" index="21"> +confirm a successful commit + <doc> + This method confirms to the client that the commit succeeded. + Note that if a commit fails, the server raises a channel exception. + </doc> + <chassis name="client" implement="MUST"/> + </method> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="rollback" synchronous="1" index="30"> +abandon the current transaction + <doc> + This method abandons all messages published and acknowledged in + the current transaction. A new transaction starts immediately + after a rollback. + </doc> + <chassis name="server" implement="MUST"/> + <response name="rollback-ok"/> + </method> + <method name="rollback-ok" synchronous="1" index="31"> +confirm a successful rollback + <doc> + This method confirms to the client that the rollback succeeded. + Note that if an rollback fails, the server raises a channel exception. + </doc> + <chassis name="client" implement="MUST"/> + </method> + </class> + <class name="dtx" handler="channel" index="100"> + <!-- +====================================================== +== DISTRIBUTED TRANSACTIONS +====================================================== +--> + work with distributed transactions + +<doc> + Distributed transactions provide so-called "2-phase commit". The + AMQP distributed transaction model supports the X-Open XA + architecture and other distributed transaction implementations. + The Dtx class assumes that the server has a private communications + channel (not AMQP) to a distributed transaction coordinator. +</doc> + <doc name="grammar"> + dtx = C:SELECT S:SELECT-OK + C:START S:START-OK +</doc> + <chassis name="server" implement="MAY"/> + <chassis name="client" implement="MAY"/> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="select" synchronous="1" index="10"> +select standard transaction mode + <doc> + This method sets the channel to use distributed transactions. The + client must use this method at least once on a channel before + using the Start method. + </doc> + <chassis name="server" implement="MUST"/> + <response name="select-ok"/> + </method> + <method name="select-ok" synchronous="1" index="11"> +confirm transaction mode + <doc> + This method confirms to the client that the channel was successfully + set to use distributed transactions. + </doc> + <chassis name="client" implement="MUST"/> + </method> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="start" synchronous="1" index="20"> + start a new distributed transaction + <doc> + This method starts a new distributed transaction. This must be + the first method on a new channel that uses the distributed + transaction mode, before any methods that publish or consume + messages. + </doc> + <chassis name="server" implement="MAY"/> + <response name="start-ok"/> + <field name="dtx identifier" type="shortstr"> + transaction identifier + <doc> + The distributed transaction key. This identifies the transaction + so that the AMQP server can coordinate with the distributed + transaction coordinator. + </doc> + <assert check="notnull"/> + </field> + </method> + <method name="start-ok" synchronous="1" index="21"> + confirm the start of a new distributed transaction + <doc> + This method confirms to the client that the transaction started. + Note that if a start fails, the server raises a channel exception. + </doc> + <chassis name="client" implement="MUST"/> + </method> + </class> + <class name="tunnel" handler="tunnel" index="110"> + <!-- +====================================================== +== TUNNEL +====================================================== +--> + methods for protocol tunneling. + +<doc> + The tunnel methods are used to send blocks of binary data - which + can be serialised AMQP methods or other protocol frames - between + AMQP peers. +</doc> + <doc name="grammar"> + tunnel = C:REQUEST + / S:REQUEST +</doc> + <chassis name="server" implement="MAY"/> + <chassis name="client" implement="MAY"/> + <field name="headers" type="table"> + Message header field table +</field> + <field name="proxy name" type="shortstr"> + The identity of the tunnelling proxy +</field> + <field name="data name" type="shortstr"> + The name or type of the message being tunnelled +</field> + <field name="durable" type="octet"> + The message durability indicator +</field> + <field name="broadcast" type="octet"> + The message broadcast mode +</field> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="request" content="1" index="10"> + sends a tunnelled method + <doc> + This method tunnels a block of binary data, which can be an + encoded AMQP method or other data. The binary data is sent + as the content for the Tunnel.Request method. + </doc> + <chassis name="server" implement="MUST"/> + <field name="meta data" type="table"> + meta data for the tunnelled block + <doc> + This field table holds arbitrary meta-data that the sender needs + to pass to the recipient. + </doc> + </field> + </method> + </class> + <class name="test" handler="channel" index="120"> + <!-- +====================================================== +== TEST - CHECK FUNCTIONAL CAPABILITIES OF AN IMPLEMENTATION +====================================================== +--> + test functional primitives of the implementation + +<doc> + The test class provides methods for a peer to test the basic + operational correctness of another peer. The test methods are + intended to ensure that all peers respect at least the basic + elements of the protocol, such as frame and content organisation + and field types. We assume that a specially-designed peer, a + "monitor client" would perform such tests. +</doc> + <doc name="grammar"> + test = C:INTEGER S:INTEGER-OK + / S:INTEGER C:INTEGER-OK + / C:STRING S:STRING-OK + / S:STRING C:STRING-OK + / C:TABLE S:TABLE-OK + / S:TABLE C:TABLE-OK + / C:CONTENT S:CONTENT-OK + / S:CONTENT C:CONTENT-OK +</doc> + <chassis name="server" implement="MUST"/> + <chassis name="client" implement="SHOULD"/> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="integer" synchronous="1" index="10"> + test integer handling + <doc> + This method tests the peer's capability to correctly marshal integer + data. + </doc> + <chassis name="client" implement="MUST"/> + <chassis name="server" implement="MUST"/> + <response name="integer-ok"/> + <field name="integer 1" type="octet"> + octet test value + <doc> + An octet integer test value. + </doc> + </field> + <field name="integer 2" type="short"> + short test value + <doc> + A short integer test value. + </doc> + </field> + <field name="integer 3" type="long"> + long test value + <doc> + A long integer test value. + </doc> + </field> + <field name="integer 4" type="longlong"> + long-long test value + <doc> + A long long integer test value. + </doc> + </field> + <field name="operation" type="octet"> + operation to test + <doc> + The client must execute this operation on the provided integer + test fields and return the result. + </doc> + <assert check="enum"> + <value name="add">return sum of test values</value> + <value name="min">return lowest of test values</value> + <value name="max">return highest of test values</value> + </assert> + </field> + </method> + <method name="integer-ok" synchronous="1" index="11"> + report integer test result + <doc> + This method reports the result of an Integer method. + </doc> + <chassis name="client" implement="MUST"/> + <chassis name="server" implement="MUST"/> + <field name="result" type="longlong"> + result value + <doc> + The result of the tested operation. + </doc> + </field> + </method> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="string" synchronous="1" index="20"> + test string handling + <doc> + This method tests the peer's capability to correctly marshal string + data. + </doc> + <chassis name="client" implement="MUST"/> + <chassis name="server" implement="MUST"/> + <response name="string-ok"/> + <field name="string 1" type="shortstr"> + short string test value + <doc> + An short string test value. + </doc> + </field> + <field name="string 2" type="longstr"> + long string test value + <doc> + A long string test value. + </doc> + </field> + <field name="operation" type="octet"> + operation to test + <doc> + The client must execute this operation on the provided string + test fields and return the result. + </doc> + <assert check="enum"> + <value name="add">return concatentation of test strings</value> + <value name="min">return shortest of test strings</value> + <value name="max">return longest of test strings</value> + </assert> + </field> + </method> + <method name="string-ok" synchronous="1" index="21"> + report string test result + <doc> + This method reports the result of a String method. + </doc> + <chassis name="client" implement="MUST"/> + <chassis name="server" implement="MUST"/> + <field name="result" type="longstr"> + result value + <doc> + The result of the tested operation. + </doc> + </field> + </method> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="table" synchronous="1" index="30"> + test field table handling + <doc> + This method tests the peer's capability to correctly marshal field + table data. + </doc> + <chassis name="client" implement="MUST"/> + <chassis name="server" implement="MUST"/> + <response name="table-ok"/> + <field name="table" type="table"> + field table of test values + <doc> + A field table of test values. + </doc> + </field> + <field name="integer op" type="octet"> + operation to test on integers + <doc> + The client must execute this operation on the provided field + table integer values and return the result. + </doc> + <assert check="enum"> + <value name="add">return sum of numeric field values</value> + <value name="min">return min of numeric field values</value> + <value name="max">return max of numeric field values</value> + </assert> + </field> + <field name="string op" type="octet"> + operation to test on strings + <doc> + The client must execute this operation on the provided field + table string values and return the result. + </doc> + <assert check="enum"> + <value name="add">return concatenation of string field values</value> + <value name="min">return shortest of string field values</value> + <value name="max">return longest of string field values</value> + </assert> + </field> + </method> + <method name="table-ok" synchronous="1" index="31"> + report table test result + <doc> + This method reports the result of a Table method. + </doc> + <chassis name="client" implement="MUST"/> + <chassis name="server" implement="MUST"/> + <field name="integer result" type="longlong"> + integer result value + <doc> + The result of the tested integer operation. + </doc> + </field> + <field name="string result" type="longstr"> + string result value + <doc> + The result of the tested string operation. + </doc> + </field> + </method> + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <method name="content" synchronous="1" content="1" index="40"> + test content handling + <doc> + This method tests the peer's capability to correctly marshal content. + </doc> + <chassis name="client" implement="MUST"/> + <chassis name="server" implement="MUST"/> + <response name="content-ok"/> + </method> + <method name="content-ok" synchronous="1" content="1" index="41"> + report content test result + <doc> + This method reports the result of a Content method. It contains the + content checksum and echoes the original content as provided. + </doc> + <chassis name="client" implement="MUST"/> + <chassis name="server" implement="MUST"/> + <field name="content checksum" type="long"> + content hash + <doc> + The 32-bit checksum of the content, calculated by adding the + content into a 32-bit accumulator. + </doc> + </field> + </method> + </class> +</amqp> diff --git a/qpid/gentools/xml-src/amqp-0.9.test.xml b/qpid/gentools/xml-src/amqp-0.9.test.xml new file mode 100644 index 0000000000..e12e9c787a --- /dev/null +++ b/qpid/gentools/xml-src/amqp-0.9.test.xml @@ -0,0 +1,4282 @@ +<?xml version = "1.0"?> + +<!-- + EDITORS: (PH) Pieter Hintjens <ph@imatix.com> + (KvdR) Kim van der Riet <kim.vdriet@redhat.com> + + These editors have been assigned by the AMQP working group. + Please do not edit/commit this file without consulting with + one of the above editors. + ======================================================== + + TODOs + - see TODO comments in the text +--> + +<!-- + Copyright Notice + ================ + (c) Copyright JPMorgan Chase Bank & Co., Cisco Systems, Inc., Envoy Technologies Inc., + iMatix Corporation, IONA\ufffd Technologies, Red Hat, Inc., + TWIST Process Innovations, and 29West Inc. 2006. All rights reserved. + + License + ======= + JPMorgan Chase Bank & Co., Cisco Systems, Inc., Envoy Technologies Inc., iMatix + Corporation, IONA\ufffd Technologies, Red Hat, Inc., TWIST Process Innovations, and + 29West Inc. (collectively, the "Authors") each hereby grants to you a worldwide, + perpetual, royalty-free, nontransferable, nonexclusive license to + (i) copy, display, and implement the Advanced Messaging Queue Protocol + ("AMQP") Specification and (ii) the Licensed Claims that are held by + the Authors, all for the purpose of implementing the Advanced Messaging + Queue Protocol Specification. Your license and any rights under this + Agreement will terminate immediately without notice from + any Author if you bring any claim, suit, demand, or action related to + the Advanced Messaging Queue Protocol Specification against any Author. + Upon termination, you shall destroy all copies of the Advanced Messaging + Queue Protocol Specification in your possession or control. + + As used hereunder, "Licensed Claims" means those claims of a patent or + patent application, throughout the world, excluding design patents and + design registrations, owned or controlled, or that can be sublicensed + without fee and in compliance with the requirements of this + Agreement, by an Author or its affiliates now or at any + future time and which would necessarily be infringed by implementation + of the Advanced Messaging Queue Protocol Specification. A claim is + necessarily infringed hereunder only when it is not possible to avoid + infringing it because there is no plausible non-infringing alternative + for implementing the required portions of the Advanced Messaging Queue + Protocol Specification. Notwithstanding the foregoing, Licensed Claims + shall not include any claims other than as set forth above even if + contained in the same patent as Licensed Claims; or that read solely + on any implementations of any portion of the Advanced Messaging Queue + Protocol Specification that are not required by the Advanced Messaging + Queue Protocol Specification, or that, if licensed, would require a + payment of royalties by the licensor to unaffiliated third parties. + Moreover, Licensed Claims shall not include (i) any enabling technologies + that may be necessary to make or use any Licensed Product but are not + themselves expressly set forth in the Advanced Messaging Queue Protocol + Specification (e.g., semiconductor manufacturing technology, compiler + technology, object oriented technology, networking technology, operating + system technology, and the like); or (ii) the implementation of other + published standards developed elsewhere and merely referred to in the + body of the Advanced Messaging Queue Protocol Specification, or + (iii) any Licensed Product and any combinations thereof the purpose or + function of which is not required for compliance with the Advanced + Messaging Queue Protocol Specification. For purposes of this definition, + the Advanced Messaging Queue Protocol Specification shall be deemed to + include both architectural and interconnection requirements essential + for interoperability and may also include supporting source code artifacts + where such architectural, interconnection requirements and source code + artifacts are expressly identified as being required or documentation to + achieve compliance with the Advanced Messaging Queue Protocol Specification. + + As used hereunder, "Licensed Products" means only those specific portions + of products (hardware, software or combinations thereof) that implement + and are compliant with all relevant portions of the Advanced Messaging + Queue Protocol Specification. + + The following disclaimers, which you hereby also acknowledge as to any + use you may make of the Advanced Messaging Queue Protocol Specification: + + THE ADVANCED MESSAGING QUEUE PROTOCOL SPECIFICATION IS PROVIDED "AS IS," + AND THE AUTHORS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR + IMPLIED, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, OR TITLE; THAT THE + CONTENTS OF THE ADVANCED MESSAGING QUEUE PROTOCOL SPECIFICATION ARE + SUITABLE FOR ANY PURPOSE; NOR THAT THE IMPLEMENTATION OF THE ADVANCED + MESSAGING QUEUE PROTOCOL SPECIFICATION WILL NOT INFRINGE ANY THIRD PARTY + PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. + + THE AUTHORS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL, + INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATING TO ANY + USE, IMPLEMENTATION OR DISTRIBUTION OF THE ADVANCED MESSAGING QUEUE + PROTOCOL SPECIFICATION. + + The name and trademarks of the Authors may NOT be used in any manner, + including advertising or publicity pertaining to the Advanced Messaging + Queue Protocol Specification or its contents without specific, written + prior permission. Title to copyright in the Advanced Messaging Queue + Protocol Specification will at all times remain with the Authors. + + No other rights are granted by implication, estoppel or otherwise. + + Upon termination of your license or rights under this Agreement, you + shall destroy all copies of the Advanced Messaging Queue Protocol + Specification in your possession or control. + + Trademarks + ========== + "JPMorgan", "JPMorgan Chase", "Chase", the JPMorgan Chase logo and the + Octagon Symbol are trademarks of JPMorgan Chase & Co. + + IMATIX and the iMatix logo are trademarks of iMatix Corporation sprl. + + IONA, IONA Technologies, and the IONA logos are trademarks of IONA + Technologies PLC and/or its subsidiaries. + + LINUX is a trademark of Linus Torvalds. RED HAT and JBOSS are registered + trademarks of Red Hat, Inc. in the US and other countries. + + Java, all Java-based trademarks and OpenOffice.org are trademarks of + Sun Microsystems, Inc. in the United States, other countries, or both. + + Other company, product, or service names may be trademarks or service + marks of others. + + Links to full AMQP specification: + ================================= + http://www.envoytech.org/spec/amq/ + http://www.iona.com/opensource/amqp/ + http://www.redhat.com/solutions/specifications/amqp/ + http://www.twiststandards.org/tiki-index.php?page=AMQ + http://www.imatix.com/amqp +--> + +<!-- + <!DOCTYPE amqp SYSTEM "amqp.dtd"> +--> + +<!-- XML Notes + + We use entities to indicate repetition; attributes to indicate properties. + + We use the 'name' attribute as an identifier, usually within the context + of the surrounding entities. + + We use spaces to seperate words in names, so that we can print names in + their natural form depending on the context - underlines for source code, + hyphens for written text, etc. + + We do not enforce any particular validation mechanism but we support all + mechanisms. The protocol definition conforms to a formal grammar that is + published seperately in several technologies. + + --> + +<amqp major = "0" minor = "9" port = "5672" comment = "AMQ Protocol"> + <!-- + ====================================================== + == CONSTANTS + ====================================================== + --> + <!-- Frame types --> + <constant name = "frame-method" value = "1" /> + <constant name = "frame-header" value = "2" /> + <constant name = "frame-body" value = "3" /> + <constant name = "frame-oob-method" value = "4" /> + <constant name = "frame-oob-header" value = "5" /> + <constant name = "frame-oob-body" value = "6" /> + <constant name = "frame-trace" value = "7" /> + <constant name = "frame-heartbeat" value = "8" /> + + <!-- Protocol constants --> + <constant name = "frame-min-size" value = "4096" /> + <constant name = "frame-end" value = "206" /> + + <!-- Reply codes --> + <constant name = "reply-success" value = "200"> + <doc> + Indicates that the method completed successfully. This reply code is + reserved for future use - the current protocol design does not use positive + confirmation and reply codes are sent only in case of an error. + </doc> + </constant> + + <constant name = "not-delivered" value = "310" class = "soft-error"> + <doc> + The client asked for a specific message that is no longer available. + The message was delivered to another client, or was purged from the queue + for some other reason. + </doc> + </constant> + + <constant name = "content-too-large" value = "311" class = "soft-error"> + <doc> + The client attempted to transfer content larger than the server could accept + at the present time. The client may retry at a later time. + </doc> + </constant> + + <constant name = "connection-forced" value = "320" class = "hard-error"> + <doc> + An operator intervened to close the connection for some reason. The client + may retry at some later date. + </doc> + </constant> + + <constant name = "invalid-path" value = "402" class = "hard-error"> + <doc> + The client tried to work with an unknown virtual host. + </doc> + </constant> + + <constant name = "access-refused" value = "403" class = "soft-error"> + <doc> + The client attempted to work with a server entity to which it has no + access due to security settings. + </doc> + </constant> + + <constant name = "not-found" value = "404" class = "soft-error"> + <doc>The client attempted to work with a server entity that does not exist.</doc> + </constant> + + <constant name = "resource-locked" value = "405" class = "soft-error"> + <doc> + The client attempted to work with a server entity to which it has no + access because another client is working with it. + </doc> + </constant> + + <constant name = "precondition-failed" value = "406" class = "soft-error"> + <doc> + The client requested a method that was not allowed because some precondition + failed. + </doc> + </constant> + + <constant name = "frame-error" value = "501" class = "hard-error"> + <doc> + The client sent a malformed frame that the server could not decode. This + strongly implies a programming error in the client. + </doc> + </constant> + + <constant name = "syntax-error" value = "502" class = "hard-error"> + <doc> + The client sent a frame that contained illegal values for one or more + fields. This strongly implies a programming error in the client. + </doc> + </constant> + + <constant name = "command-invalid" value = "503" class = "hard-error"> + <doc> + The client sent an invalid sequence of frames, attempting to perform an + operation that was considered invalid by the server. This usually implies + a programming error in the client. + </doc> + </constant> + + <constant name = "channel-error" value = "504" class = "hard-error"> + <doc> + The client attempted to work with a channel that had not been correctly + opened. This most likely indicates a fault in the client layer. + </doc> + </constant> + + <constant name = "resource-error" value = "506" class = "hard-error"> + <doc> + The server could not complete the method because it lacked sufficient + resources. This may be due to the client creating too many of some type + of entity. + </doc> + </constant> + + <constant name = "not-allowed" value = "530" class = "hard-error"> + <doc> + The client tried to work with some entity in a manner that is prohibited + by the server, due to security settings or by some other criteria. + </doc> + </constant> + + <constant name = "not-implemented" value = "540" class = "hard-error"> + <doc> + The client tried to use functionality that is not implemented in the + server. + </doc> + </constant> + + <constant name = "internal-error" value = "541" class = "hard-error"> + <doc> + The server could not complete the method because of an internal error. + The server may require intervention by an operator in order to resume + normal operations. + </doc> + </constant> + + <constant name = "test-str2" value = "1.2.3.3"/> + + <!-- + ====================================================== + == DOMAIN TYPES + ====================================================== + --> + + <domain name = "access-ticket" type = "short" label = "access ticket granted by server"> + <doc> + An access ticket granted by the server for a certain set of access rights + within a specific realm. Access tickets are valid within the channel where + they were created, and expire when the channel closes. + </doc> + <assert check = "ne" value = "0" /> + </domain> + + <domain name = "class-id" type = "short" /> + + <domain name = "consumer-tag" type = "shortstr" label = "consumer tag"> + <doc> + Identifier for the consumer, valid within the current connection. + </doc> + </domain> + + <domain name = "delivery-tag" type = "longlong" label = "server-assigned delivery tag"> + <doc> + The server-assigned and channel-specific delivery tag + </doc> + <rule name = "channel-local"> + <doc> + The delivery tag is valid only within the channel from which the message was + received. I.e. a client MUST NOT receive a message on one channel and then + acknowledge it on another. + </doc> + </rule> + <rule name = "non-zero"> + <doc> + The server MUST NOT use a zero value for delivery tags. Zero is reserved + for client use, meaning "all messages so far received". + </doc> + </rule> + </domain> + + <domain name = "exchange-name" type = "shortstr" label = "exchange name"> + <doc> + The exchange name is a client-selected string that identifies the exchange for publish + methods. Exchange names may consist of any mixture of digits, letters, and underscores. + Exchange names are scoped by the virtual host. + </doc> + <assert check = "length" value = "127" /> + </domain> + + <domain name = "known-hosts" type = "shortstr" label = "list of known hosts"> + <doc> + Specifies the list of equivalent or alternative hosts that the server knows about, + which will normally include the current server itself. Clients can cache this + information and use it when reconnecting to a server after a failure. This field + may be empty. + </doc> + </domain> + + <domain name = "method-id" type = "short" /> + + <domain name = "no-ack" type = "bit" label = "no acknowledgement needed"> + <doc> + If this field is set the server does not expect acknowledgments for + messages. That is, when a message is delivered to the client the server + automatically and silently acknowledges it on behalf of the client. This + functionality increases performance but at the cost of reliability. + Messages can get lost if a client dies before it can deliver them to the + application. + </doc> + </domain> + + <domain name = "no-local" type = "bit" label = "do not deliver own messages"> + <doc> + If the no-local field is set the server will not send messages to the client that + published them. + </doc> + </domain> + + <domain name = "path" type = "shortstr"> + <doc> + Must start with a slash "/" and continue with path names separated by slashes. A path + name consists of any combination of at least one of [A-Za-z0-9] plus zero or more of + [.-_+!=:]. + </doc> + + <assert check = "notnull" /> + <assert check = "syntax" rule = "path" /> + <assert check = "length" value = "127" /> + </domain> + + <domain name = "peer-properties" type = "table"> + <doc> + This string provides a set of peer properties, used for identification, debugging, and + general information. + </doc> + </domain> + + <domain name = "queue-name" type = "shortstr" label = "queue name"> + <doc> + The queue name identifies the queue within the vhost. Queue names may consist of any + mixture of digits, letters, and underscores. + </doc> + <assert check = "length" value = "127" /> + </domain> + + <domain name = "redelivered" type = "bit" label = "message is being redelivered"> + <doc> + This indicates that the message has been previously delivered to this or + another client. + </doc> + <rule name = "implementation"> + <doc> + The server SHOULD try to signal redelivered messages when it can. When + redelivering a message that was not successfully acknowledged, the server + SHOULD deliver it to the original client if possible. + </doc> + <doc type = "scenario"> + Create a shared queue and publish a message to the queue. Consume the + message using explicit acknowledgements, but do not acknowledge the + message. Close the connection, reconnect, and consume from the queue + again. The message should arrive with the redelivered flag set. + </doc> + </rule> + <rule name = "hinting"> + <doc> + The client MUST NOT rely on the redelivered field but should take it as a + hint that the message may already have been processed. A fully robust + client must be able to track duplicate received messages on non-transacted, + and locally-transacted channels. + </doc> + </rule> + </domain> + + <domain name = "reply-code" type = "short" label = "reply code from server"> + <doc> + The reply code. The AMQ reply codes are defined as constants at the start + of this formal specification. + </doc> + <assert check = "notnull" /> + </domain> + + <domain name = "reply-text" type = "shortstr" label = "localised reply text"> + <doc> + The localised reply text. This text can be logged as an aid to resolving + issues. + </doc> + <assert check = "notnull" /> + </domain> + + <!-- Elementary domains --> + <domain name = "bit" type = "bit" label = "single bit" /> + <domain name = "octet" type = "octet" label = "single octet" /> + <domain name = "short" type = "short" label = "16-bit integer" /> + <domain name = "long" type = "long" label = "32-bit integer" /> + <domain name = "longlong" type = "longlong" label = "64-bit integer" /> + <domain name = "shortstr" type = "shortstr" label = "short string" /> + <domain name = "longstr" type = "longstr" label = "long string" /> + <domain name = "timestamp" type = "timestamp" label = "64-bit timestamp" /> + <domain name = "table" type = "table" label = "field table" /> + + <!-- == CONNECTION ======================================================= --> + + <!-- TODO 0.81 - the 'handler' attribute of methods needs to be reviewed, and if + no current implementations use it, removed. /PH 2006/07/20 + --> + + <class name = "connection" handler = "connection" index = "10" label = "work with socket connections"> + <doc> + The connection class provides methods for a client to establish a network connection to + a server, and for both peers to operate the connection thereafter. + </doc> + + <doc type = "grammar"> + connection = open-connection *use-connection close-connection + open-connection = C:protocol-header + S:START C:START-OK + *challenge + S:TUNE C:TUNE-OK + C:OPEN S:OPEN-OK | S:REDIRECT + challenge = S:SECURE C:SECURE-OK + use-connection = *channel + close-connection = C:CLOSE S:CLOSE-OK + / S:CLOSE C:CLOSE-OK + </doc> + + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "start" synchronous = "1" index = "10" label = "start connection negotiation"> + <doc> + This method starts the connection negotiation process by telling the client the + protocol version that the server proposes, along with a list of security mechanisms + which the client can use for authentication. + </doc> + + <rule name = "protocol-name"> + <doc> + If the server cannot support the protocol specified in the protocol header, + it MUST close the socket connection without sending any response method. + </doc> + <doc type = "scenario"> + The client sends a protocol header containing an invalid protocol name. + The server must respond by closing the connection. + </doc> + </rule> + <rule name = "server-support"> + <doc> + The server MUST provide a protocol version that is lower than or equal to + that requested by the client in the protocol header. + </doc> + <doc type = "scenario"> + The client requests a protocol version that is higher than any valid + implementation, e.g. 9.0. The server must respond with a current + protocol version, e.g. 1.0. + </doc> + </rule> + <rule name = "client-support"> + <doc> + If the client cannot handle the protocol version suggested by the server + it MUST close the socket connection. + </doc> + <doc type = "scenario"> + The server sends a protocol version that is lower than any valid + implementation, e.g. 0.1. The client must respond by closing the + connection. + </doc> + </rule> + + <chassis name = "client" implement = "MUST" /> + <response name = "start-ok" /> + + <field name = "version-major" domain = "octet" label = "protocol major version"> + <doc> + The version of the protocol, expressed in protocol units of 0.1 public + versions and properly printed as two digits with a leading zero. I.e. a + protocol version of "09" represents a public version "0.9". The decimal + shift allows the correct expression of pre-1.0 protocol releases. + </doc> + <doc type = "todo"> + This field should be renamed to "protocol version". + </doc> + </field> + + <field name = "version-minor" domain = "octet" label = "protocol major version"> + <doc> + The protocol revision, expressed as an integer from 0 to 9. The use of more + than ten revisions is discouraged. The public version string is constructed + from the protocol version and revision as follows: we print the protocol + version with one decimal position, and we append the protocol revision. A + version=10 and revision=2 are printed as "1.02". + </doc> + <doc type = "todo"> + This field should be renamed to "protocol revision". + </doc> + </field> + + <field name = "server-properties" domain = "peer-properties" label = "server properties"> + <rule name = "required-fields"> + <doc> + The properties SHOULD contain at least these fields: "host", specifying the + server host name or address, "product", giving the name of the server product, + "version", giving the name of the server version, "platform", giving the name + of the operating system, "copyright", if appropriate, and "information", giving + other general information. + </doc> + <doc type = "scenario"> + Client connects to server and inspects the server properties. It checks for + the presence of the required fields. + </doc> + </rule> + </field> + + <field name = "mechanisms" domain = "longstr" label = "available security mechanisms"> + <doc> + A list of the security mechanisms that the server supports, delimited by spaces. + Currently ASL supports these mechanisms: PLAIN. + </doc> + <assert check = "notnull" /> + </field> + + <field name = "locales" domain = "longstr" label = "available message locales"> + <doc> + A list of the message locales that the server supports, delimited by spaces. The + locale defines the language in which the server will send reply texts. + </doc> + <rule name = "required-support"> + <doc> + The server MUST support at least the en_US locale. + </doc> + <doc type = "scenario"> + Client connects to server and inspects the locales field. It checks for + the presence of the required locale(s). + </doc> + </rule> + <assert check = "notnull" /> + </field> + </method> + + <method name = "start-ok" synchronous = "1" index = "11" + label = "select security mechanism and locale"> + <doc> + This method selects a SASL security mechanism. ASL uses SASL (RFC2222) to + negotiate authentication and encryption. + </doc> + + <chassis name = "server" implement = "MUST" /> + + <field name = "client-properties" domain = "peer-properties" label = "client properties"> + <rule name = "required-fields"> + <!-- This rule is not testable from the client side --> + <doc> + The properties SHOULD contain at least these fields: "product", giving the name + of the client product, "version", giving the name of the client version, "platform", + giving the name of the operating system, "copyright", if appropriate, and + "information", giving other general information. + </doc> + </rule> + </field> + + <field name = "mechanism" domain = "shortstr" label = "selected security mechanism"> + <doc> + A single security mechanisms selected by the client, which must be one of those + specified by the server. + </doc> + <rule name = "security"> + <doc> + The client SHOULD authenticate using the highest-level security profile it + can handle from the list provided by the server. + </doc> + </rule> + <rule name = "validity"> + <doc> + If the mechanism field does not contain one of the security mechanisms + proposed by the server in the Start method, the server MUST close the + connection without sending any further data. + </doc> + <doc type = "scenario"> + Client connects to server and sends an invalid security mechanism. The + server must respond by closing the connection (a socket close, with no + connection close negotiation). + </doc> + </rule> + <assert check = "notnull" /> + </field> + + <field name = "response" domain = "longstr" label = "security response data"> + <doc> + A block of opaque data passed to the security mechanism. The contents of this + data are defined by the SASL security mechanism. + </doc> + <assert check = "notnull" /> + </field> + + <field name = "locale" domain = "shortstr" label = "selected message locale"> + <doc> + A single message local selected by the client, which must be one of those + specified by the server. + </doc> + <assert check = "notnull" /> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "secure" synchronous = "1" index = "20" label = "security mechanism challenge"> + <doc> + The SASL protocol works by exchanging challenges and responses until both peers have + received sufficient information to authenticate each other. This method challenges + the client to provide more information. + </doc> + + <chassis name = "client" implement = "MUST" /> + <response name = "secure-ok" /> + + <field name = "challenge" domain = "longstr" label = "security challenge data"> + <doc> + Challenge information, a block of opaque binary data passed to the security + mechanism. + </doc> + </field> + </method> + + <method name = "secure-ok" synchronous = "1" index = "21" label = "security mechanism response"> + <doc> + This method attempts to authenticate, passing a block of SASL data for the security + mechanism at the server side. + </doc> + + <chassis name = "server" implement = "MUST" /> + + <field name = "response" domain = "longstr" label = "security response data"> + <doc> + A block of opaque data passed to the security mechanism. The contents of this + data are defined by the SASL security mechanism. + </doc> + <assert check = "notnull" /> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "tune" synchronous = "1" index = "30" + label = "propose connection tuning parameters"> + <doc> + This method proposes a set of connection configuration values to the client. The + client can accept and/or adjust these. + </doc> + + <chassis name = "client" implement = "MUST" /> + + <response name = "tune-ok" /> + + <field name = "channel-max" domain = "short" label = "proposed maximum channels"> + <doc> + The maximum total number of channels that the server allows per connection. Zero + means that the server does not impose a fixed limit, but the number of allowed + channels may be limited by available server resources. + </doc> + </field> + + <field name = "frame-max" domain = "long" label = "proposed maximum frame size"> + <doc> + The largest frame size that the server proposes for the connection. The client + can negotiate a lower value. Zero means that the server does not impose any + specific limit but may reject very large frames if it cannot allocate resources + for them. + </doc> + <rule name = "minimum"> + <doc> + Until the frame-max has been negotiated, both peers MUST accept frames of up + to frame-min-size octets large, and the minimum negotiated value for frame-max + is also frame-min-size. + </doc> + <doc type = "scenario"> + Client connects to server and sends a large properties field, creating a frame + of frame-min-size octets. The server must accept this frame. + </doc> + </rule> + </field> + + <field name = "heartbeat" domain = "short" label = "desired heartbeat delay"> + <!-- TODO 0.82 - the heartbeat negotiation mechanism was changed during + implementation because the model documented here does not actually + work properly. The best model we found is that the server proposes + a heartbeat value to the client; the client can reply with zero, meaning + 'do not use heartbeats (as documented here), or can propose its own + heartbeat value, which the server should then accept. This is different + from the model here which is disconnected - e.g. each side requests a + heartbeat independently. Basically a connection is heartbeated in + both ways, or not at all, depending on whether both peers support + heartbeating or not, and the heartbeat value should itself be chosen + by the client so that remote links can get a higher value. Also, the + actual heartbeat mechanism needs documentation, and is as follows: so + long as there is activity on a connection - in or out - both peers + assume the connection is active. When there is no activity, each peer + must send heartbeat frames. When no heartbeat frame is received after + N cycles (where N is at least 2), the connection can be considered to + have died. /PH 2006/07/19 + --> + <doc> + The delay, in seconds, of the connection heartbeat that the server wants. + Zero means the server does not want a heartbeat. + </doc> + </field> + </method> + + <method name = "tune-ok" synchronous = "1" index = "31" + label = "negotiate connection tuning parameters"> + <doc> + This method sends the client's connection tuning parameters to the server. + Certain fields are negotiated, others provide capability information. + </doc> + + <chassis name = "server" implement = "MUST" /> + + <field name = "channel-max" domain = "short" label = "negotiated maximum channels"> + <doc> + The maximum total number of channels that the client will use per connection. + </doc> + <rule name = "upper-limit"> + <doc> + If the client specifies a channel max that is higher than the value provided + by the server, the server MUST close the connection without attempting a + negotiated close. The server may report the error in some fashion to assist + implementors. + </doc> + </rule> + <assert check = "notnull" /> + <assert check = "le" method = "tune" field = "channel-max" /> + </field> + + <field name = "frame-max" domain = "long" label = "negotiated maximum frame size"> + <doc> + The largest frame size that the client and server will use for the connection. + Zero means that the client does not impose any specific limit but may reject + very large frames if it cannot allocate resources for them. Note that the + frame-max limit applies principally to content frames, where large contents can + be broken into frames of arbitrary size. + </doc> + <rule name = "minimum"> + <doc> + Until the frame-max has been negotiated, both peers MUST accept frames of up + to frame-min-size octets large, and the minimum negotiated value for frame-max + is also frame-min-size. + </doc> + </rule> + <rule name = "upper-limit"> + <doc> + If the client specifies a frame max that is higher than the value provided + by the server, the server MUST close the connection without attempting a + negotiated close. The server may report the error in some fashion to assist + implementors. + </doc> + </rule> + </field> + + <field name = "heartbeat" domain = "short" label = "desired heartbeat delay"> + <doc> + The delay, in seconds, of the connection heartbeat that the client wants. Zero + means the client does not want a heartbeat. + </doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "open" synchronous = "1" index = "40" label = "open connection to virtual host"> + <doc> + This method opens a connection to a virtual host, which is a collection of + resources, and acts to separate multiple application domains within a server. + The server may apply arbitrary limits per virtual host, such as the number + of each type of entity that may be used, per connection and/or in total. + </doc> + + <chassis name = "server" implement = "MUST" /> + <response name = "open-ok" /> + <response name = "redirect" /> + + <field name = "virtual-host" domain = "path" label = "virtual host name"> + <!-- TODO 0.82 - the entire vhost model needs review. This concept was + prompted by the HTTP vhost concept but does not fit very well into + AMQP. Currently we use the vhost as a "cluster identifier" which is + inaccurate usage. /PH 2006/07/19 + --> + <assert check = "regexp" value = "^[a-zA-Z0-9/-_]+$" /> + <doc> + The name of the virtual host to work with. + </doc> + <rule name = "separation"> + <doc> + If the server supports multiple virtual hosts, it MUST enforce a full + separation of exchanges, queues, and all associated entities per virtual + host. An application, connected to a specific virtual host, MUST NOT be able + to access resources of another virtual host. + </doc> + </rule> + <rule name = "security"> + <doc> + The server SHOULD verify that the client has permission to access the + specified virtual host. + </doc> + </rule> + </field> + + <field name = "capabilities" domain = "shortstr" label = "required capabilities"> + <doc> + The client can specify zero or more capability names, delimited by spaces. + The server can use this string to how to process the client's connection + request. + </doc> + </field> + + <field name = "insist" domain = "bit" label = "insist on connecting to server"> + <doc> + In a configuration with multiple collaborating servers, the server may respond + to a Connection.Open method with a Connection.Redirect. The insist option tells + the server that the client is insisting on a connection to the specified server. + </doc> + <rule name = "behaviour"> + <doc> + When the client uses the insist option, the server MUST NOT respond with a + Connection.Redirect method. If it cannot accept the client's connection + request it should respond by closing the connection with a suitable reply + code. + </doc> + </rule> + </field> + </method> + + <method name = "open-ok" synchronous = "1" index = "41" label = "signal that connection is ready"> + <doc> + This method signals to the client that the connection is ready for use. + </doc> + <chassis name = "client" implement = "MUST" /> + <field name = "known-hosts" domain = "known-hosts" /> + </method> + + <method name = "redirect" synchronous = "1" index = "42" label = "redirects client to other server"> + <doc> + This method redirects the client to another server, based on the requested virtual + host and/or capabilities. + </doc> + <rule name = "usage"> + <doc> + When getting the Connection.Redirect method, the client SHOULD reconnect to + the host specified, and if that host is not present, to any of the hosts + specified in the known-hosts list. + </doc> + </rule> + <chassis name = "client" implement = "MUST" /> + <field name = "host" domain = "shortstr" label = "server to connect to"> + <doc> + Specifies the server to connect to. This is an IP address or a DNS name, + optionally followed by a colon and a port number. If no port number is + specified, the client should use the default port number for the protocol. + </doc> + <assert check = "notnull" /> + </field> + <field name = "known-hosts" domain = "known-hosts" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "close" synchronous = "1" index = "50" label = "request a connection close"> + <doc> + This method indicates that the sender wants to close the connection. This may be + due to internal conditions (e.g. a forced shut-down) or due to an error handling + a specific method, i.e. an exception. When a close is due to an exception, the + sender provides the class and method id of the method which caused the exception. + </doc> + <!-- TODO: the connection close mechanism needs to be reviewed from the ODF + documentation and better expressed as rules here. /PH 2006/07/20 + --> + <rule name = "stability"> + <doc> + After sending this method any received method except the Close-OK method MUST + be discarded. + </doc> + </rule> + + <chassis name = "client" implement = "MUST" /> + <chassis name = "server" implement = "MUST" /> + <response name = "close-ok" /> + + <field name = "reply-code" domain = "reply-code" /> + <field name = "reply-text" domain = "reply-text" /> + + <field name = "class-id" domain = "class-id" label = "failing method class"> + <doc> + When the close is provoked by a method exception, this is the class of the + method. + </doc> + </field> + + <field name = "method-id" domain = "method-id" label = "failing method ID"> + <doc> + When the close is provoked by a method exception, this is the ID of the method. + </doc> + </field> + </method> + + <method name = "close-ok" synchronous = "1" index = "51" label = "confirm a connection close"> + <doc> + This method confirms a Connection.Close method and tells the recipient that it is + safe to release resources for the connection and close the socket. + </doc> + <rule name = "reporting"> + <doc> + A peer that detects a socket closure without having received a Close-Ok + handshake method SHOULD log the error. + </doc> + </rule> + <chassis name = "client" implement = "MUST" /> + <chassis name = "server" implement = "MUST" /> + </method> + </class> + + <!-- == CHANNEL ========================================================== --> + + <class name = "channel" handler = "channel" index = "20" label = "work with channels"> + <doc> + The channel class provides methods for a client to establish a channel to a + server and for both peers to operate the channel thereafter. + </doc> + + <doc type = "grammar"> + channel = open-channel *use-channel close-channel + open-channel = C:OPEN S:OPEN-OK + use-channel = C:FLOW S:FLOW-OK + / S:FLOW C:FLOW-OK + / S:ALERT + / functional-class + close-channel = C:CLOSE S:CLOSE-OK + / S:CLOSE C:CLOSE-OK + </doc> + + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "open" synchronous = "1" index = "10" label = "open a channel for use"> + <doc> + This method opens a channel to the server. + </doc> + <rule name = "state" on-failure = "channel-error"> + <doc> + The client MUST NOT use this method on an alread-opened channel. + </doc> + <doc type = "scenario"> + Client opens a channel and then reopens the same channel. + </doc> + </rule> + <chassis name = "server" implement = "MUST" /> + <response name = "open-ok" /> + <field name = "out of band" domain = "shortstr" label = "out-of-band settings"> + <doc> + Configures out-of-band transfers on this channel. The syntax and meaning of this + field will be formally defined at a later date. + </doc> + <assert check = "null" /> + </field> + </method> + + <method name = "open-ok" synchronous = "1" index = "11" label = "signal that the channel is ready"> + <doc> + This method signals to the client that the channel is ready for use. + </doc> + <chassis name = "client" implement = "MUST" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "flow" synchronous = "1" index = "20" label = "enable/disable flow from peer"> + <doc> + This method asks the peer to pause or restart the flow of content data. This is a + simple flow-control mechanism that a peer can use to avoid oveflowing its queues or + otherwise finding itself receiving more messages than it can process. Note that this + method is not intended for window control. The peer that receives a disable flow + method should finish sending the current content frame, if any, then pause. + </doc> + + <rule name = "initial-state"> + <doc> + When a new channel is opened, it is active (flow is active). Some applications + assume that channels are inactive until started. To emulate this behaviour a + client MAY open the channel, then pause it. + </doc> + </rule> + + <rule name = "bidirectional"> + <doc> + When sending content frames, a peer SHOULD monitor the channel for incoming + methods and respond to a Channel.Flow as rapidly as possible. + </doc> + </rule> + + <rule name = "throttling"> + <doc> + A peer MAY use the Channel.Flow method to throttle incoming content data for + internal reasons, for example, when exchanging data over a slower connection. + </doc> + </rule> + + <rule name = "expected-behaviour"> + <doc> + The peer that requests a Channel.Flow method MAY disconnect and/or ban a peer + that does not respect the request. This is to prevent badly-behaved clients + from overwhelming a broker. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> + + <response name = "flow-ok" /> + + <field name = "active" domain = "bit" label = "start/stop content frames"> + <doc> + If 1, the peer starts sending content frames. If 0, the peer stops sending + content frames. + </doc> + </field> + </method> + + <method name = "flow-ok" index = "21" label = "confirm a flow method"> + <doc> + Confirms to the peer that a flow command was received and processed. + </doc> + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> + <field name = "active" domain = "bit" label = "current flow setting"> + <doc> + Confirms the setting of the processed flow method: 1 means the peer will start + sending or continue to send content frames; 0 means it will not. + </doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- TODO 0.82 - remove this method entirely + /PH 2006/07/20 + --> + <method name = "alert" index = "30" label = "send a non-fatal warning message"> + <doc> + This method allows the server to send a non-fatal warning to the client. This is + used for methods that are normally asynchronous and thus do not have confirmations, + and for which the server may detect errors that need to be reported. Fatal errors + are handled as channel or connection exceptions; non-fatal errors are sent through + this method. + </doc> + <chassis name = "client" implement = "MUST" /> + <field name = "reply-code" domain = "reply-code" /> + <field name = "reply-text" domain = "reply-text" /> + <field name = "details" domain = "table" label = "detailed information for warning"> + <doc> + A set of fields that provide more information about the problem. The meaning of + these fields are defined on a per-reply-code basis (TO BE DEFINED). + </doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "close" synchronous = "1" index = "40" label = "request a channel close"> + <doc> + This method indicates that the sender wants to close the channel. This may be due to + internal conditions (e.g. a forced shut-down) or due to an error handling a specific + method, i.e. an exception. When a close is due to an exception, the sender provides + the class and method id of the method which caused the exception. + </doc> + + <!-- TODO: the channel close behaviour needs to be reviewed from the ODF + documentation and better expressed as rules here. /PH 2006/07/20 + --> + <rule name = "stability"> + <doc> + After sending this method any received method except the Close-OK method MUST + be discarded. + </doc> + </rule> + + <chassis name = "client" implement = "MUST" /> + <chassis name = "server" implement = "MUST" /> + <response name = "close-ok" /> + + <field name = "reply-code" domain = "reply-code" /> + <field name = "reply-text" domain = "reply-text" /> + + <field name = "class-id" domain = "class-id" label = "failing method class"> + <doc> + When the close is provoked by a method exception, this is the class of the + method. + </doc> + </field> + + <field name = "method-id" domain = "method-id" label = "failing method ID"> + <doc> + When the close is provoked by a method exception, this is the ID of the method. + </doc> + </field> + </method> + + <method name = "close-ok" synchronous = "1" index = "41" label = "confirm a channel close"> + <doc> + This method confirms a Channel.Close method and tells the recipient that it is safe + to release resources for the channel and close the socket. + </doc> + <rule name = "reporting"> + <doc> + A peer that detects a socket closure without having received a Channel.Close-Ok + handshake method SHOULD log the error. + </doc> + </rule> + <chassis name = "client" implement = "MUST" /> + <chassis name = "server" implement = "MUST" /> + </method> + </class> + + <!-- == ACCESS =========================================================== --> + + <!-- TODO 0.82 - this class must be implemented by two teams before we can + consider it matured. + --> + + <class name = "access" handler = "connection" index = "30" label = "work with access tickets"> + <doc> + The protocol control access to server resources using access tickets. A + client must explicitly request access tickets before doing work. An access + ticket grants a client the right to use a specific set of resources - + called a "realm" - in specific ways. + </doc> + + <doc type = "grammar"> + access = C:REQUEST S:REQUEST-OK + </doc> + + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "request" synchronous = "1" index = "10" label = "request an access ticket"> + <doc> + This method requests an access ticket for an access realm. The server + responds by granting the access ticket. If the client does not have + access rights to the requested realm this causes a connection exception. + Access tickets are a per-channel resource. + </doc> + + <chassis name = "server" implement = "MUST" /> + <response name = "request-ok" /> + + <field name = "realm" domain = "shortstr" label = "name of requested realm"> + <doc> + Specifies the name of the realm to which the client is requesting access. + The realm is a configured server-side object that collects a set of + resources (exchanges, queues, etc.). If the channel has already requested + an access ticket onto this realm, the previous ticket is destroyed and a + new ticket is created with the requested access rights, if allowed. + </doc> + <rule name = "validity" on-failure = "access-refused"> + <doc> + The client MUST specify a realm that is known to the server. The server + makes an identical response for undefined realms as it does for realms + that are defined but inaccessible to this client. + </doc> + <doc type = "scenario"> + Client specifies an undefined realm. + </doc> + </rule> + </field> + + <field name = "exclusive" domain = "bit" label = "request exclusive access"> + <doc> + Request exclusive access to the realm, meaning that this will be the only + channel that uses the realm's resources. + </doc> + <rule name = "validity" on-failure = "access-refused"> + <doc> + The client MAY NOT request exclusive access to a realm that has active + access tickets, unless the same channel already had the only access + ticket onto that realm. + </doc> + <doc type = "scenario"> + Client opens two channels and requests exclusive access to the same realm. + </doc> + </rule> + </field> + <field name = "passive" domain = "bit" label = "request passive access"> + <doc> + Request message passive access to the specified access realm. Passive + access lets a client get information about resources in the realm but + not to make any changes to them. + </doc> + </field> + <field name = "active" domain = "bit" label = "request active access"> + <doc> + Request message active access to the specified access realm. Active access lets + a client get create and delete resources in the realm. + </doc> + </field> + <field name = "write" domain = "bit" label = "request write access"> + <doc> + Request write access to the specified access realm. Write access lets a client + publish messages to all exchanges in the realm. + </doc> + </field> + <field name = "read" domain = "bit" label = "request read access"> + <doc> + Request read access to the specified access realm. Read access lets a client + consume messages from queues in the realm. + </doc> + </field> + </method> + + <method name = "request-ok" synchronous = "1" index = "11" label = "grant access to server resources"> + <doc> + This method provides the client with an access ticket. The access ticket is valid + within the current channel and for the lifespan of the channel. + </doc> + <rule name = "per-channel" on-failure = "not-allowed"> + <doc> + The client MUST NOT use access tickets except within the same channel as + originally granted. + </doc> + <doc type = "scenario"> + Client opens two channels, requests a ticket on one channel, and then + tries to use that ticket in a seconc channel. + </doc> + </rule> + <chassis name = "client" implement = "MUST" /> + <field name = "ticket" domain = "access-ticket" /> + </method> + </class> + + <!-- == EXCHANGE ========================================================= --> + + <class name = "exchange" handler = "channel" index = "40" label = "work with exchanges"> + <doc> + Exchanges match and distribute messages across queues. Exchanges can be configured in + the server or created at runtime. + </doc> + + <doc type = "grammar"> + exchange = C:DECLARE S:DECLARE-OK + / C:DELETE S:DELETE-OK + </doc> + + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> + + <rule name = "required-types"> + <doc> + The server MUST implement these standard exchange types: fanout, direct. + </doc> + <doc type = "scenario"> + Client attempts to declare an exchange with each of these standard types. + </doc> + </rule> + <rule name = "recommended-types"> + <doc> + The server SHOULD implement these standard exchange types: topic, headers. + </doc> + <doc type = "scenario"> + Client attempts to declare an exchange with each of these standard types. + </doc> + </rule> + <rule name = "required-instances"> + <doc> + The server MUST, in each virtual host, pre-declare an exchange instance + for each standard exchange type that it implements, where the name of the + exchange instance is "amq." followed by the exchange type name. + </doc> + <doc type = "scenario"> + Client creates a temporary queue and attempts to bind to each required + exchange instance (amq.fanout, amq.direct, and amq.topic, amq.headers if + those types are defined). + </doc> + </rule> + <rule name = "default-exchange"> + <doc> + The server MUST predeclare a direct exchange to act as the default exchange + for content Publish methods and for default queue bindings. + </doc> + <doc type = "scenario"> + Client checks that the default exchange is active by specifying a queue + binding with no exchange name, and publishing a message with a suitable + routing key but without specifying the exchange name, then ensuring that + the message arrives in the queue correctly. + </doc> + </rule> + <rule name = "default-access"> + <doc> + The server MUST NOT allow clients to access the default exchange except + by specifying an empty exchange name in the Queue.Bind and content Publish + methods. + </doc> + </rule> + <rule name = "extensions"> + <doc> + The server MAY implement other exchange types as wanted. + </doc> + </rule> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "declare" synchronous = "1" index = "10" label = "declare exchange, create if needed"> + <doc> + This method creates an exchange if it does not already exist, and if the exchange + exists, verifies that it is of the correct and expected class. + </doc> + <rule name = "minimum"> + <doc> + The server SHOULD support a minimum of 16 exchanges per virtual host and + ideally, impose no limit except as defined by available resources. + </doc> + <doc type = "scenario"> + The client creates as many exchanges as it can until the server reports + an error; the number of exchanges successfuly created must be at least + sixteen. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + <response name = "declare-ok" /> + + <field name = "ticket" domain = "access-ticket"> + <doc> + When a client defines a new exchange, this belongs to the access realm of the + ticket used. All further work done with that exchange must be done with an + access ticket for the same realm. + </doc> + <rule name = "validity" on-failure = "access-refused"> + <doc> + The client MUST provide a valid access ticket giving "active" access to + the realm in which the exchange exists or will be created, or "passive" + access if the if-exists flag is set. + </doc> + <doc type = "scenario"> + Client creates access ticket with wrong access rights and attempts to use + in this method. + </doc> + </rule> + </field> + + <field name = "exchange" domain = "exchange-name"> + <rule name = "reserved" on-failure = "access-refused"> + <doc> + Exchange names starting with "amq." are reserved for predeclared and + standardised exchanges. The client MUST NOT attempt to create an exchange + starting with "amq.". + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + <assert check = "regexp" value = "^[a-zA-Z0-9-_.:]+$" /> + </field> + + <field name = "type" domain = "shortstr" label = "exchange type"> + <doc> + Each exchange belongs to one of a set of exchange types implemented by the + server. The exchange types define the functionality of the exchange - i.e. how + messages are routed through it. It is not valid or meaningful to attempt to + change the type of an existing exchange. + </doc> + <rule name = "typed" on-failure = "not-allowed"> + <doc> + Exchanges cannot be redeclared with different types. The client MUST not + attempt to redeclare an existing exchange with a different type than used + in the original Exchange.Declare method. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + <rule name = "support" on-failure = "command-invalid"> + <doc> + The client MUST NOT attempt to create an exchange with a type that the + server does not support. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + <assert check = "regexp" value = "^[a-zA-Z0-9-_.:]+$" /> + </field> + + <field name = "passive" domain = "bit" label = "do not create exchange"> + <doc> + If set, the server will not create the exchange. The client can use this to + check whether an exchange exists without modifying the server state. + </doc> + <rule name = "not-found"> + <doc> + If set, and the exchange does not already exist, the server MUST raise a + channel exception with reply code 404 (not found). + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + </field> + + <field name = "durable" domain = "bit" label = "request a durable exchange"> + <doc> + If set when creating a new exchange, the exchange will be marked as durable. + Durable exchanges remain active when a server restarts. Non-durable exchanges + (transient exchanges) are purged if/when a server restarts. + </doc> + <rule name = "support"> + <doc> + The server MUST support both durable and transient exchanges. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + <rule name = "sticky"> + <doc> + The server MUST ignore the durable field if the exchange already exists. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + </field> + + <!-- TODO 0.82 - clarify how this works; there is no way to cancel a binding + except by deleting a queue. + --> + <field name = "auto-delete" domain = "bit" label = "auto-delete when unused"> + <doc> + If set, the exchange is deleted when all queues have finished using it. + </doc> + <rule name = "sticky"> + <doc> + The server MUST ignore the auto-delete field if the exchange already + exists. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + </field> + + <field name = "internal" domain = "bit" label = "create internal exchange"> + <doc> + If set, the exchange may not be used directly by publishers, but only when bound + to other exchanges. Internal exchanges are used to construct wiring that is not + visible to applications. + </doc> + </field> + + <field name = "nowait" domain = "bit" label = "do not send reply method"> + <doc> + If set, the server will not respond to the method. The client should not wait + for a reply method. If the server could not complete the method it will raise a + channel or connection exception. + </doc> + </field> + + <field name = "arguments" domain = "table" label = "arguments for declaration"> + <doc> + A set of arguments for the declaration. The syntax and semantics of these + arguments depends on the server implementation. This field is ignored if passive + is 1. + </doc> + </field> + </method> + + <method name = "declare-ok" synchronous = "1" index = "11" label = "confirm exchange declaration"> + <doc> + This method confirms a Declare method and confirms the name of the exchange, + essential for automatically-named exchanges. + </doc> + <chassis name = "client" implement = "MUST" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "delete" synchronous = "1" index = "20" label = "delete an exchange"> + <doc> + This method deletes an exchange. When an exchange is deleted all queue bindings on + the exchange are cancelled. + </doc> + + <chassis name = "server" implement = "MUST" /> + <response name = "delete-ok" /> + + <field name = "ticket" domain = "access-ticket"> + <rule name = "validity" on-failure = "access-refused"> + <doc> + The client MUST provide a valid access ticket giving "active" access + rights to the exchange's access realm. + </doc> + <doc type = "scenario"> + Client creates access ticket with wrong access rights and attempts to use + in this method. + </doc> + </rule> + </field> + + <field name = "exchange" domain = "exchange-name"> + <rule name = "exists" on-failure = "not-found"> + <doc> + The client MUST NOT attempt to delete an exchange that does not exist. + </doc> + </rule> + <assert check = "notnull" /> + </field> + + <!-- TODO 0.82 - discuss whether this option is useful or not. I don't have + any real use case for it. /PH 2006-07-23. + --> + <field name = "if-unused" domain = "bit" label = "delete only if unused"> + <doc> + If set, the server will only delete the exchange if it has no queue bindings. If + the exchange has queue bindings the server does not delete it but raises a + channel exception instead. + </doc> + </field> + + <field name = "nowait" domain = "bit" label = "do not send a reply method"> + <doc> + If set, the server will not respond to the method. The client should not wait + for a reply method. If the server could not complete the method it will raise a + channel or connection exception. + </doc> + </field> + </method> + + <method name = "delete-ok" synchronous = "1" index = "21" + label = "confirm deletion of an exchange"> + <doc>This method confirms the deletion of an exchange.</doc> + <chassis name = "client" implement = "MUST" /> + </method> + </class> + + <!-- == QUEUE ============================================================ --> + + <class name = "queue" handler = "channel" index = "50" label = "work with queues"> + <doc> + Queues store and forward messages. Queues can be configured in the server or created at + runtime. Queues must be attached to at least one exchange in order to receive messages + from publishers. + </doc> + + <doc type = "grammar"> + queue = C:DECLARE S:DECLARE-OK + / C:BIND S:BIND-OK + / C:PURGE S:PURGE-OK + / C:DELETE S:DELETE-OK + </doc> + + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> + + <rule name = "any-content"> + <doc> + A server MUST allow any content class to be sent to any queue, in any mix, and + queue and deliver these content classes independently. Note that all methods + that fetch content off queues are specific to a given content class. + </doc> + <doc type = "scenario"> + Client creates an exchange of each standard type and several queues that + it binds to each exchange. It must then sucessfully send each of the standard + content types to each of the available queues. + </doc> + </rule> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "declare" synchronous = "1" index = "10" label = "declare queue, create if needed"> + <doc> + This method creates or checks a queue. When creating a new queue the client can + specify various properties that control the durability of the queue and its + contents, and the level of sharing for the queue. + </doc> + + <rule name = "default-binding"> + <doc> + The server MUST create a default binding for a newly-created queue to the + default exchange, which is an exchange of type 'direct'. + </doc> + <doc type = "scenario"> + Client creates a new queue, and then without explicitly binding it to an + exchange, attempts to send a message through the default exchange binding, + i.e. publish a message to the empty exchange, with the queue name as routing + key. + </doc> + </rule> + + <!-- Rule test name: was "amq_queue_35" --> + <rule name = "minimum-queues"> + <doc> + The server SHOULD support a minimum of 256 queues per virtual host and ideally, + impose no limit except as defined by available resources. + </doc> + <doc type = "scenario"> + Client attempts to create as many queues as it can until the server reports + an error. The resulting count must at least be 256. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + <response name = "declare-ok" /> + + <field name = "ticket" domain = "access-ticket"> + <doc> + When a client defines a new queue, this belongs to the access realm of the + ticket used. All further work done with that queue must be done with an access + ticket for the same realm. + </doc> + <rule name = "validity" on-failure = "access-refused"> + <doc> + The client MUST provide a valid access ticket giving "active" access to + the realm in which the queue exists or will be created. + </doc> + <doc type = "scenario"> + Client creates access ticket with wrong access rights and attempts to use + in this method. + </doc> + </rule> + </field> + + <field name = "queue" domain = "queue-name"> + <rule name = "default-name"> + <doc> + The queue name MAY be empty, in which case the server MUST create a new + queue with a unique generated name and return this to the client in the + Declare-Ok method. + </doc> + <doc type = "scenario"> + Client attempts to create several queues with an empty name. The client then + verifies that the server-assigned names are unique and different. + </doc> + </rule> + <rule name = "reserved-prefix" on-failure = "not-allowed"> + <doc> + Queue names starting with "amq." are reserved for predeclared and + standardised server queues. A client MAY NOT attempt to declare a queue with a + name that starts with "amq." and the passive option set to zero. + </doc> + <doc type = "scenario"> + A client attempts to create a queue with a name starting with "amq." and with + the passive option set to zero. + </doc> + </rule> + <assert check = "regexp" value = "^[a-zA-Z0-9-_.:]*$" /> + </field> + + <field name = "passive" domain = "bit" label = "do not create queue"> + <doc> + If set, the server will not create the queue. This field allows the client + to assert the presence of a queue without modifying the server state. + </doc> + <rule name = "passive" on-failure = "not-found"> + <doc> + The client MAY ask the server to assert that a queue exists without + creating the queue if not. If the queue does not exist, the server + treats this as a failure. + </doc> + <doc type = "scenario"> + Client declares an existing queue with the passive option and expects + the server to respond with a declare-ok. Client then attempts to declare + a non-existent queue with the passive option, and the server must close + the channel with the correct reply-code. + </doc> + </rule> + </field> + + <field name = "durable" domain = "bit" label = "request a durable queue"> + <doc> + If set when creating a new queue, the queue will be marked as durable. Durable + queues remain active when a server restarts. Non-durable queues (transient + queues) are purged if/when a server restarts. Note that durable queues do not + necessarily hold persistent messages, although it does not make sense to send + persistent messages to a transient queue. + </doc> + <!-- Rule test name: was "amq_queue_03" --> + <rule name = "persistence"> + <doc>The server MUST recreate the durable queue after a restart.</doc> + + <!-- TODO: use 'client does something' rather than 'a client does something'. --> + <doc type = "scenario"> + A client creates a durable queue. The server is then restarted. The client + then attempts to send a message to the queue. The message should be successfully + delivered. + </doc> + </rule> + <!-- Rule test name: was "amq_queue_36" --> + <rule name = "types"> + <doc>The server MUST support both durable and transient queues.</doc> + <doc type = "scenario"> + A client creates two named queues, one durable and one transient. + </doc> + </rule> + <!-- Rule test name: was "amq_queue_37" --> + <rule name = "pre-existence"> + <doc>The server MUST ignore the durable field if the queue already exists.</doc> + <doc type = "scenario"> + A client creates two named queues, one durable and one transient. The client + then attempts to declare the two queues using the same names again, but reversing + the value of the durable flag in each case. Verify that the queues still exist + with the original durable flag values. + <!-- TODO: but how? --> + </doc> + </rule> + </field> + + <field name = "exclusive" domain = "bit" label = "request an exclusive queue"> + <doc> + Exclusive queues may only be consumed from by the current connection. Setting + the 'exclusive' flag always implies 'auto-delete'. + </doc> + + <!-- Rule test name: was "amq_queue_38" --> + <rule name = "types"> + <doc> + The server MUST support both exclusive (private) and non-exclusive (shared) + queues. + </doc> + <doc type = "scenario"> + A client creates two named queues, one exclusive and one non-exclusive. + </doc> + </rule> + + <!-- Rule test name: was "amq_queue_04" --> + <rule name = "02" on-failure = "channel-error"> + <doc> + The client MAY NOT attempt to declare any existing and exclusive queue + on multiple connections. + </doc> + <doc type = "scenario"> + A client declares an exclusive named queue. A second client on a different + connection attempts to declare a queue of the same name. + </doc> + </rule> + </field> + + <field name = "auto-delete" domain = "bit" label = "auto-delete queue when unused"> + <doc> + If set, the queue is deleted when all consumers have finished using it. Last + consumer can be cancelled either explicitly or because its channel is closed. If + there was no consumer ever on the queue, it won't be deleted. + </doc> + + <!-- Rule test name: was "amq_queue_31" --> + <rule name = "pre-existence"> + <doc> + The server MUST ignore the auto-delete field if the queue already exists. + </doc> + <doc type = "scenario"> + A client creates two named queues, one as auto-delete and one explicit-delete. + The client then attempts to declare the two queues using the same names again, + but reversing the value of the auto-delete field in each case. Verify that the + queues still exist with the original auto-delete flag values. + <!-- TODO: but how? --> + </doc> + </rule> + </field> + + <field name = "nowait" domain = "bit" label = "do not send a reply method"> + <doc> + If set, the server will not respond to the method. The client should not wait + for a reply method. If the server could not complete the method it will raise a + channel or connection exception. + </doc> + </field> + + <field name = "arguments" domain = "table" label = "arguments for declaration"> + <doc> + A set of arguments for the declaration. The syntax and semantics of these + arguments depends on the server implementation. This field is ignored if passive + is 1. + </doc> + </field> + </method> + + <method name = "declare-ok" synchronous = "1" index = "11" label = "confirms a queue definition"> + <doc> + This method confirms a Declare method and confirms the name of the queue, essential + for automatically-named queues. + </doc> + + <chassis name = "client" implement = "MUST" /> + + <field name = "queue" domain = "queue-name"> + <doc> + Reports the name of the queue. If the server generated a queue name, this field + contains that name. + </doc> + <assert check = "notnull" /> + </field> + + <field name = "message-count" domain = "long" label = "number of messages in queue"> + <doc> + Reports the number of messages in the queue, which will be zero for + newly-created queues. + </doc> + </field> + + <field name = "consumer-count" domain = "long" label = "number of consumers"> + <doc> + Reports the number of active consumers for the queue. Note that consumers can + suspend activity (Channel.Flow) in which case they do not appear in this count. + </doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "bind" synchronous = "1" index = "20" label = "bind queue to an exchange"> + <doc> + This method binds a queue to an exchange. Until a queue is bound it will not receive + any messages. In a classic messaging model, store-and-forward queues are bound to a + dest exchange and subscription queues are bound to a dest_wild exchange. + </doc> + + <!-- Rule test name: was "amq_queue_25" --> + <rule name = "duplicates"> + <doc> + A server MUST allow ignore duplicate bindings - that is, two or more bind + methods for a specific queue, with identical arguments - without treating these + as an error. + </doc> + <doc type = "scenario"> + A client binds a named queue to an exchange. The client then repeats the bind + (with identical arguments). + </doc> + </rule> + + <!-- Rule test name: was "amq_queue_39" --> + <rule name = "failure" on-failure = "??????"> + <!-- + TODO: Find correct code. The on-failure code returned should depend on why the bind + failed. Assuming that failures owing to bad parameters are covered in the rules relating + to those parameters, the only remaining reason for a failure would be the lack of + server resorces or some internal error - such as too many queues open. Would these + cases qualify as "resource error" 506 or "internal error" 541? + --> + <doc>If a bind fails, the server MUST raise a connection exception.</doc> + <doc type = "scenario"> + TODO + </doc> + </rule> + + <!-- Rule test name: was "amq_queue_12" --> + <rule name = "transient-exchange" on-failure = "not-allowed"> + <doc> + The server MUST NOT allow a durable queue to bind to a transient exchange. + </doc> + <doc type = "scenario"> + A client creates a transient exchange. The client then declares a named durable + queue and then attempts to bind the transient exchange to the durable queue. + </doc> + </rule> + + <!-- Rule test name: was "amq_queue_13" --> + <rule name = "durable-exchange"> + <doc> + Bindings for durable queues are automatically durable and the server SHOULD + restore such bindings after a server restart. + </doc> + <doc type = "scenario"> + A server creates a named durable queue and binds it to a durable exchange. The + server is restarted. The client then attempts to use the queue/exchange combination. + </doc> + </rule> + + <!-- Rule test name: was "amq_queue_17" --> + <rule name = "internal-exchange"> + <doc> + If the client attempts to bind to an exchange that was declared as internal, the server + MUST raise a connection exception with reply code 530 (not allowed). + </doc> + <doc type = "scenario"> + A client attempts to bind a named queue to an internal exchange. + </doc> + </rule> + + <!-- Rule test name: was "amq_queue_40" --> + <rule name = "binding-count"> + <doc> + The server SHOULD support at least 4 bindings per queue, and ideally, impose no + limit except as defined by available resources. + </doc> + <doc type = "scenario"> + A client creates a named queue and attempts to bind it to 4 different non-internal + exchanges. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + + <response name = "bind-ok" /> + + <field name = "ticket" domain = "access-ticket"> + <doc> + The client provides a valid access ticket giving "active" access rights to the + queue's access realm. + </doc> + </field> + + <field name = "queue" domain = "queue-name"> + <doc> + Specifies the name of the queue to bind. If the queue name is empty, refers to + the current queue for the channel, which is the last declared queue. + </doc> + + <rule name = "empty-queue" on-failure = "not-allowed"> + <doc> + A client MUST NOT be allowed to bind a non-existent and unnamed queue (i.e. + empty queue name) to an exchange. + </doc> + <doc type = "scenario"> + A client attempts to bind with an unnamed (empty) queue name to an exchange. + </doc> + </rule> + + <!-- Rule test name: was "amq_queue_26" --> + <rule name = "queue-existence" on-failure = "not-found"> + <doc> + A client MUST NOT be allowed to bind a non-existent queue (i.e. not previously + declared) to an exchange. + </doc> + <doc type = "scenario"> + A client attempts to bind an undeclared queue name to an exchange. + </doc> + </rule> + </field> + + <field name = "exchange" domain = "exchange-name" label = "name of the exchange to bind to"> + <!-- Rule test name: was "amq_queue_14" --> + <rule name = "exchange-existence" on-failure = "not-found"> + <doc> + A client MUST NOT be allowed to bind a queue to a non-existent exchange. + </doc> + <doc type = "scenario"> + A client attempts to bind an named queue to a undeclared exchange. + </doc> + </rule> + </field> + + <field name = "routing-key" domain = "shortstr" label = "message routing key"> + <doc> + Specifies the routing key for the binding. The routing key is used for routing + messages depending on the exchange configuration. Not all exchanges use a + routing key - refer to the specific exchange documentation. If the queue name + is empty, the server uses the last queue declared on the channel. If the + routing key is also empty, the server uses this queue name for the routing + key as well. If the queue name is provided but the routing key is empty, the + server does the binding with that empty routing key. The meaning of empty + routing keys depends on the exchange implementation. + </doc> + </field> + + <field name = "nowait" domain = "bit" label = "do not send a reply method"> + <doc> + If set, the server will not respond to the method. The client should not wait + for a reply method. If the server could not complete the method it will raise a + channel or connection exception. + </doc> + </field> + + <field name = "arguments" domain = "table" label = "arguments for binding"> + <doc> + A set of arguments for the binding. The syntax and semantics of these arguments + depends on the exchange class. + </doc> + </field> + </method> + + <method name = "bind-ok" synchronous = "1" index = "21" label = "confirm bind successful"> + <doc>This method confirms that the bind was successful.</doc> + + <chassis name = "client" implement = "MUST" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "purge" synchronous = "1" index = "30" label = "purge a queue"> + <doc> + This method removes all messages from a queue. It does not cancel consumers. Purged + messages are deleted without any formal "undo" mechanism. + </doc> + + <!-- Rule test name: was "amq_queue_15" --> + <rule name = "01"> + <doc>A call to purge MUST result in an empty queue.</doc> + </rule> + + <!-- Rule test name: was "amq_queue_41" --> + <rule name = "02"> + <doc> + On transacted channels the server MUST not purge messages that have already been + sent to a client but not yet acknowledged. + </doc> + </rule> + + <!-- TODO: Rule split? --> + + <!-- Rule test name: was "amq_queue_42" --> + <rule name = "03"> + <doc> + The server MAY implement a purge queue or log that allows system administrators + to recover accidentally-purged messages. The server SHOULD NOT keep purged + messages in the same storage spaces as the live messages since the volumes of + purged messages may get very large. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + + <response name = "purge-ok" /> + + <field name = "ticket" domain = "access-ticket"> + <doc>The access ticket must be for the access realm that holds the queue.</doc> + + <rule name = "01"> + <doc> + The client MUST provide a valid access ticket giving "read" access rights to + the queue's access realm. Note that purging a queue is equivalent to reading + all messages and discarding them. + </doc> + </rule> + </field> + + <field name = "queue" domain = "queue-name"> + <doc> + Specifies the name of the queue to purge. If the queue name is empty, refers to + the current queue for the channel, which is the last declared queue. + </doc> + + <rule name = "01"> + <doc> + If the client did not previously declare a queue, and the queue name in this + method is empty, the server MUST raise a connection exception with reply + code 530 (not allowed). + </doc> + </rule> + + <!-- TODO Rule split? --> + + <!-- Rule test name: was "amq_queue_16" --> + <rule name = "02"> + <doc> + The queue MUST exist. Attempting to purge a non-existing queue MUST cause a + channel exception. + </doc> + </rule> + </field> + + <field name = "nowait" domain = "bit" label = "do not send a reply method"> + <doc> + If set, the server will not respond to the method. The client should not wait + for a reply method. If the server could not complete the method it will raise a + channel or connection exception. + </doc> + </field> + </method> + + <method name = "purge-ok" synchronous = "1" index = "31" label = "confirms a queue purge"> + <doc>This method confirms the purge of a queue.</doc> + + <chassis name = "client" implement = "MUST" /> + + <field name = "message-count" domain = "long" label = "number of messages purged"> + <doc>Reports the number of messages purged.</doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "delete" synchronous = "1" index = "40" label = "delete a queue"> + <doc> + This method deletes a queue. When a queue is deleted any pending messages are sent + to a dead-letter queue if this is defined in the server configuration, and all + consumers on the queue are cancelled. + </doc> + + <!-- TODO: Rule split? --> + + <!-- Rule test name: was "amq_queue_43" --> + <rule name = "01"> + <doc> + The server SHOULD use a dead-letter queue to hold messages that were pending on + a deleted queue, and MAY provide facilities for a system administrator to move + these messages back to an active queue. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + + <response name = "delete-ok" /> + + <field name = "ticket" domain = "access-ticket"> + <doc> + The client provides a valid access ticket giving "active" access rights to the + queue's access realm. + </doc> + </field> + + <field name = "queue" domain = "queue-name"> + <doc> + Specifies the name of the queue to delete. If the queue name is empty, refers to + the current queue for the channel, which is the last declared queue. + </doc> + + <rule name = "01"> + <doc> + If the client did not previously declare a queue, and the queue name in this + method is empty, the server MUST raise a connection exception with reply + code 530 (not allowed). + </doc> + </rule> + + <!-- Rule test name: was "amq_queue_21" --> + <rule name = "02"> + <doc> + The queue must exist. If the client attempts to delete a non-existing queue + the server MUST raise a channel exception with reply code 404 (not found). + </doc> + </rule> + </field> + + <field name = "if-unused" domain = "bit" label = "delete only if unused"> + <doc> + If set, the server will only delete the queue if it has no consumers. If the + queue has consumers the server does does not delete it but raises a channel + exception instead. + </doc> + + <!-- Rule test name: was "amq_queue_29" and "amq_queue_30" --> + <rule name = "01"> + <doc>The server MUST respect the if-unused flag when deleting a queue.</doc> + </rule> + </field> + + <field name = "if-empty" domain = "bit" label = "delete only if empty"> + <doc> + If set, the server will only delete the queue if it has no messages. + </doc> + <rule name = "01"> + <doc> + If the queue is not empty the server MUST raise a channel exception with + reply code 406 (precondition failed). + </doc> + </rule> + </field> + + <field name = "nowait" domain = "bit" label = "do not send a reply method"> + <doc> + If set, the server will not respond to the method. The client should not wait + for a reply method. If the server could not complete the method it will raise a + channel or connection exception. + </doc> + </field> + </method> + + <method name = "delete-ok" synchronous = "1" index = "41" label = "confirm deletion of a queue"> + <doc>This method confirms the deletion of a queue.</doc> + + <chassis name = "client" implement = "MUST" /> + + <field name = "message-count" domain = "long" label = "number of messages purged"> + <doc>Reports the number of messages purged.</doc> + </field> + </method> + </class> + + <!-- == BASIC ============================================================ --> + + <class name = "basic" handler = "channel" index = "60" label = "work with basic content"> + <doc> + The Basic class provides methods that support an industry-standard messaging model. + </doc> + + <doc type = "grammar"> + basic = C:QOS S:QOS-OK + / C:CONSUME S:CONSUME-OK + / C:CANCEL S:CANCEL-OK + / C:PUBLISH content + / S:RETURN content + / S:DELIVER content + / C:GET ( S:GET-OK content / S:GET-EMPTY ) + / C:ACK + / C:REJECT + </doc> + + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MAY" /> + + <!-- Rule test name: was "amq_basic_08" --> + <rule name = "01"> + <doc> + The server SHOULD respect the persistent property of basic messages and + SHOULD make a best-effort to hold persistent basic messages on a reliable + storage mechanism. + </doc> + <doc type = "scenario"> + Send a persistent message to queue, stop server, restart server and then + verify whether message is still present. Assumes that queues are durable. + Persistence without durable queues makes no sense. + </doc> + </rule> + + <!-- Rule test name: was "amq_basic_09" --> + <rule name = "02"> + <doc> + The server MUST NOT discard a persistent basic message in case of a queue + overflow. + </doc> + <doc type = "scenario"> + Create a queue overflow situation with persistent messages and verify that + messages do not get lost (presumably the server will write them to disk). + </doc> + </rule> + + <rule name = "03"> + <doc> + The server MAY use the Channel.Flow method to slow or stop a basic message + publisher when necessary. + </doc> + <doc type = "scenario"> + Create a queue overflow situation with non-persistent messages and verify + whether the server responds with Channel.Flow or not. Repeat with persistent + messages. + </doc> + </rule> + + <!-- Rule test name: was "amq_basic_10" --> + <rule name = "04"> + <doc> + The server MAY overflow non-persistent basic messages to persistent + storage. + </doc> + <!-- Test scenario: untestable --> + </rule> + + <rule name = "05"> + <doc> + The server MAY discard or dead-letter non-persistent basic messages on a + priority basis if the queue size exceeds some configured limit. + </doc> + <!-- Test scenario: untestable --> + </rule> + + <!-- Rule test name: was "amq_basic_11" --> + <rule name = "06"> + <doc> + The server MUST implement at least 2 priority levels for basic messages, + where priorities 0-4 and 5-9 are treated as two distinct levels. + </doc> + <doc type = "scenario"> + Send a number of priority 0 messages to a queue. Send one priority 9 + message. Consume messages from the queue and verify that the first message + received was priority 9. + </doc> + </rule> + + <rule name = "07"> + <doc> + The server MAY implement up to 10 priority levels. + </doc> + <doc type = "scenario"> + Send a number of messages with mixed priorities to a queue, so that all + priority values from 0 to 9 are exercised. A good scenario would be ten + messages in low-to-high priority. Consume from queue and verify how many + priority levels emerge. + </doc> + </rule> + + <!-- Rule test name: was "amq_basic_12" --> + <rule name = "08"> + <doc> + The server MUST deliver messages of the same priority in order irrespective of + their individual persistence. + </doc> + <doc type = "scenario"> + Send a set of messages with the same priority but different persistence + settings to a queue. Consume and verify that messages arrive in same order + as originally published. + </doc> + </rule> + + <!-- Rule test name: was "amq_basic_13" --> + <rule name = "09"> + <doc> + The server MUST support automatic acknowledgements on Basic content, i.e. + consumers with the no-ack field set to FALSE. + </doc> + <doc type = "scenario"> + Create a queue and a consumer using automatic acknowledgements. Publish + a set of messages to the queue. Consume the messages and verify that all + messages are received. + </doc> + </rule> + + <rule name = "10"> + <doc> + The server MUST support explicit acknowledgements on Basic content, i.e. + consumers with the no-ack field set to TRUE. + </doc> + <doc type = "scenario"> + Create a queue and a consumer using explicit acknowledgements. Publish a + set of messages to the queue. Consume the messages but acknowledge only + half of them. Disconnect and reconnect, and consume from the queue. + Verify that the remaining messages are received. + </doc> + </rule> + + <!-- These are the properties for a Basic content --> + + <field name = "content-type" domain = "shortstr" label = "MIME content type" /> + <field name = "content-encoding" domain = "shortstr" label = "MIME content encoding" /> + <field name = "headers" domain = "table" label = "message header field table" /> + <field name = "delivery-mode" domain = "octet" label = "non-persistent (1) or persistent (2)" /> + <field name = "priority" domain = "octet" label = "message priority, 0 to 9" /> + <field name = "correlation-id" domain = "shortstr" label = "application correlation identifier" /> + <field name = "reply-to" domain = "shortstr" label = "destination to reply to" /> + <field name = "expiration" domain = "shortstr" label = "message expiration specification" /> + <field name = "message-id" domain = "shortstr" label = "application message identifier" /> + <field name = "timestamp" domain = "timestamp" label = "message timestamp" /> + <field name = "type" domain = "shortstr" label = "message type name" /> + <field name = "user-id" domain = "shortstr" label = "creating user id" /> + <field name = "app-id" domain = "shortstr" label = "creating application id" /> + <!-- This field is deprecated pending review --> + <field name = "cluster-id" domain = "shortstr" label = "intra-cluster routing identifier" /> + + <field name = "property-one" domain = "shortstr" label = "Extra property for testing only" /> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "qos" synchronous = "1" index = "10" label = "specify quality of service"> + <doc> + This method requests a specific quality of service. The QoS can be specified for the + current channel or for all channels on the connection. The particular properties and + semantics of a qos method always depend on the content class semantics. Though the + qos method could in principle apply to both peers, it is currently meaningful only + for the server. + </doc> + + <chassis name = "server" implement = "MUST" /> + <response name = "qos-ok" /> + + <field name = "prefetch-size" domain = "long" label = "prefetch window in octets"> + <doc> + The client can request that messages be sent in advance so that when the client + finishes processing a message, the following message is already held locally, + rather than needing to be sent down the channel. Prefetching gives a performance + improvement. This field specifies the prefetch window size in octets. The server + will send a message in advance if it is equal to or smaller in size than the + available prefetch size (and also falls into other prefetch limits). May be set + to zero, meaning "no specific limit", although other prefetch limits may still + apply. The prefetch-size is ignored if the no-ack option is set. + </doc> + <!-- Rule test name: was "amq_basic_17" --> + <rule name = "01"> + <doc> + The server MUST ignore this setting when the client is not processing any + messages - i.e. the prefetch size does not limit the transfer of single + messages to a client, only the sending in advance of more messages while + the client still has one or more unacknowledged messages. + </doc> + <doc type = "scenario"> + Define a QoS prefetch-size limit and send a single message that exceeds + that limit. Verify that the message arrives correctly. + </doc> + </rule> + </field> + + <field name = "prefetch-count" domain = "short" label = "prefetch window in messages"> + <doc> + Specifies a prefetch window in terms of whole messages. This field may be used + in combination with the prefetch-size field; a message will only be sent in + advance if both prefetch windows (and those at the channel and connection level) + allow it. The prefetch-count is ignored if the no-ack option is set. + </doc> + <!-- Rule test name: was "amq_basic_18" --> + <rule name = "01"> + <doc> + The server may send less data in advance than allowed by the client's + specified prefetch windows but it MUST NOT send more. + </doc> + <doc type = "scenario"> + Define a QoS prefetch-size limit and a prefetch-count limit greater than + one. Send multiple messages that exceed the prefetch size. Verify that + no more than one message arrives at once. + </doc> + </rule> + </field> + + <field name = "global" domain = "bit" label = "apply to entire connection"> + <doc> + By default the QoS settings apply to the current channel only. If this field is + set, they are applied to the entire connection. + </doc> + </field> + </method> + + <method name = "qos-ok" synchronous = "1" index = "11" label = "confirm the requested qos"> + <doc> + This method tells the client that the requested QoS levels could be handled by the + server. The requested QoS applies to all active consumers until a new QoS is + defined. + </doc> + <chassis name = "client" implement = "MUST" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "consume" synchronous = "1" index = "30" label = "start a queue consumer"> + <doc> + This method asks the server to start a "consumer", which is a transient request for + messages from a specific queue. Consumers last as long as the channel they were + created on, or until the client cancels them. + </doc> + + <!-- Rule test name: was "amq_basic_01" --> + <rule name = "01"> + <doc> + The server SHOULD support at least 16 consumers per queue, and ideally, impose + no limit except as defined by available resources. + </doc> + <doc type = "scenario"> + Create a queue and create consumers on that queue until the server closes the + connection. Verify that the number of consumers created was at least sixteen + and report the total number. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + <response name = "consume-ok" /> + + <field name = "ticket" domain = "access-ticket"> + <rule name = "01" on-failure = "access-refused"> + <doc> + The client MUST provide a valid access ticket giving "read" access rights to + the realm for the queue. + </doc> + <doc type = "scenario"> + Attempt to create a consumer with an invalid (non-zero) access ticket. + </doc> + </rule> + </field> + + <field name = "queue" domain = "queue-name"> + <doc> + Specifies the name of the queue to consume from. If the queue name is null, + refers to the current queue for the channel, which is the last declared queue. + </doc> + <rule name = "01" on-failure = "not-allowed"> + <doc> + If the queue name is empty the client MUST have previously declared a + queue using this channel. + </doc> + <doc type = "scenario"> + Attempt to create a consumer with an empty queue name and no previously + declared queue on the channel. + </doc> + </rule> + </field> + + <field name = "consumer-tag" domain = "consumer-tag"> + <doc> + Specifies the identifier for the consumer. The consumer tag is local to a + connection, so two clients can use the same consumer tags. If this field is + empty the server will generate a unique tag. + </doc> + <rule name = "01" on-failure = "not-allowed"> + <doc> + The client MUST NOT specify a tag that refers to an existing consumer. + </doc> + <doc type = "scenario"> + Attempt to create two consumers with the same non-empty tag. + </doc> + </rule> + <rule name = "02" on-failure = "not-allowed"> + <doc> + The consumer tag is valid only within the channel from which the + consumer was created. I.e. a client MUST NOT create a consumer in one + channel and then use it in another. + </doc> + <doc type = "scenario"> + Attempt to create a consumer in one channel, then use in another channel, + in which consumers have also been created (to test that the server uses + unique consumer tags). + </doc> + </rule> + </field> + + <field name = "no-local" domain = "no-local" /> + + <field name = "no-ack" domain = "no-ack" /> + + <field name = "exclusive" domain = "bit" label = "request exclusive access"> + <doc> + Request exclusive consumer access, meaning only this consumer can access the + queue. + </doc> + <!-- Rule test name: was "amq_basic_02" --> + <rule name = "01" on-failure = "access-refused"> + <doc> + The client MAY NOT gain exclusive access to a queue that already has + active consumers. + </doc> + <doc type = "scenario"> + Open two connections to a server, and in one connection create a shared + (non-exclusive) queue and then consume from the queue. In the second + connection attempt to consume from the same queue using the exclusive + option. + </doc> + </rule> + </field> + + <field name = "nowait" domain = "bit" label = "do not send a reply method"> + <doc> + If set, the server will not respond to the method. The client should not wait + for a reply method. If the server could not complete the method it will raise + a channel or connection exception. + </doc> + </field> + </method> + + <method name = "consume-ok" synchronous = "1" index = "31" label = "confirm a new consumer"> + <doc> + The server provides the client with a consumer tag, which is used by the client + for methods called on the consumer at a later stage. + </doc> + <chassis name = "client" implement = "MUST" /> + <field name = "consumer-tag" domain = "consumer-tag"> + <doc> + Holds the consumer tag specified by the client or provided by the server. + </doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "cancel" synchronous = "1" index = "20" label = "end a queue consumer"> + <doc> + This method cancels a consumer. This does not affect already delivered + messages, but it does mean the server will not send any more messages for + that consumer. The client may receive an abitrary number of messages in + between sending the cancel method and receiving the cancel-ok reply. + </doc> + + <rule name = "01"> + <doc> + If the queue does not exist the server MUST ignore the cancel method, so + long as the consumer tag is valid for that channel. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + <response name = "cancel-ok" /> + + <field name = "consumer-tag" domain = "consumer-tag" /> + + <field name = "nowait" domain = "bit" label = "do not send a reply method"> + <doc> + If set, the server will not respond to the method. The client should not wait + for a reply method. If the server could not complete the method it will raise a + channel or connection exception. + </doc> + </field> + </method> + + <method name = "cancel-ok" synchronous = "1" index = "21" label = "confirm a cancelled consumer"> + <doc> + This method confirms that the cancellation was completed. + </doc> + <chassis name = "client" implement = "MUST" /> + <field name = "consumer-tag" domain = "consumer-tag" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "publish" content = "1" index = "40" label = "publish a message"> + <doc> + This method publishes a message to a specific exchange. The message will be routed + to queues as defined by the exchange configuration and distributed to any active + consumers when the transaction, if any, is committed. + </doc> + + <chassis name = "server" implement = "MUST" /> + + <field name = "ticket" domain = "access-ticket"> + <rule name = "01"> + <doc> + The client MUST provide a valid access ticket giving "write" access rights + to the access realm for the exchange. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + </field> + + <field name = "exchange" domain = "exchange-name"> + <doc> + Specifies the name of the exchange to publish to. The exchange name can be + empty, meaning the default exchange. If the exchange name is specified, and that + exchange does not exist, the server will raise a channel exception. + </doc> + + <!-- Rule test name: was "amq_basic_06" --> + <rule name = "01"> + <doc> + The server MUST accept a blank exchange name to mean the default exchange. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + + <!-- Rule test name: was "amq_basic_14" --> + <rule name = "02"> + <doc> + If the exchange was declared as an internal exchange, the server MUST raise + a channel exception with a reply code 403 (access refused). + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + + <!-- Rule test name: was "amq_basic_15" --> + <rule name = "03"> + <doc> + The exchange MAY refuse basic content in which case it MUST raise a channel + exception with reply code 540 (not implemented). + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + </field> + + <field name = "routing-key" domain = "shortstr" label = "Message routing key"> + <doc> + Specifies the routing key for the message. The routing key is used for routing + messages depending on the exchange configuration. + </doc> + </field> + + <field name = "mandatory" domain = "bit" label = "indicate mandatory routing"> + <doc> + This flag tells the server how to react if the message cannot be routed to a + queue. If this flag is set, the server will return an unroutable message with a + Return method. If this flag is zero, the server silently drops the message. + </doc> + <!-- Rule test name: was "amq_basic_07" --> + <rule name = "01"> + <doc> + The server SHOULD implement the mandatory flag. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + </field> + + <field name = "immediate" domain = "bit" label = "request immediate delivery"> + <doc> + This flag tells the server how to react if the message cannot be routed to a + queue consumer immediately. If this flag is set, the server will return an + undeliverable message with a Return method. If this flag is zero, the server + will queue the message, but with no guarantee that it will ever be consumed. + </doc> + <!-- Rule test name: was "amq_basic_16" --> + <rule name = "01"> + <doc> + The server SHOULD implement the immediate flag. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + </field> + </method> + + <method name = "return" content = "1" index = "50" label = "return a failed message"> + <doc> + This method returns an undeliverable message that was published with the "immediate" + flag set, or an unroutable message published with the "mandatory" flag set. The + reply code and text provide information about the reason that the message was + undeliverable. + </doc> + + <chassis name = "client" implement = "MUST" /> + + <field name = "reply-code" domain = "reply-code" /> + + <field name = "reply-text" domain = "reply-text" /> + + <field name = "exchange" domain = "exchange-name"> + <doc> + Specifies the name of the exchange that the message was originally published to. + </doc> + </field> + + <field name = "routing-key" domain = "shortstr" label = "Message routing key"> + <doc> + Specifies the routing key name specified when the message was published. + </doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "deliver" content = "1" index = "60" + label = "notify the client of a consumer message"> + <doc> + This method delivers a message to the client, via a consumer. In the asynchronous + message delivery model, the client starts a consumer using the Consume method, then + the server responds with Deliver methods as and when messages arrive for that + consumer. + </doc> + + <!-- Rule test name: was "amq_basic_19" --> + <rule name = "01"> + <!-- TODO: Rule split? --> + <doc> + The server SHOULD track the number of times a message has been delivered to + clients and when a message is redelivered a certain number of times - e.g. 5 + times - without being acknowledged, the server SHOULD consider the message to be + unprocessable (possibly causing client applications to abort), and move the + message to a dead letter queue. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer-tag" domain = "consumer-tag" /> + + <field name = "delivery-tag" domain = "delivery-tag" /> + + <field name = "redelivered" domain = "redelivered" /> + + <field name = "exchange" domain = "exchange-name"> + <doc> + Specifies the name of the exchange that the message was originally published to. + </doc> + </field> + + <field name = "routing-key" domain = "shortstr" label = "Message routing key"> + <doc>Specifies the routing key name specified when the message was published.</doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "get" synchronous = "1" index = "70" label = "direct access to a queue"> + <doc> + This method provides a direct access to the messages in a queue using a synchronous + dialogue that is designed for specific types of application where synchronous + functionality is more important than performance. + </doc> + + <response name = "get-ok" /> + <response name = "get-empty" /> + <chassis name = "server" implement = "MUST" /> + + <field name = "ticket" domain = "access-ticket"> + <rule name = "01"> + <doc> + The client MUST provide a valid access ticket giving "read" access rights to + the realm for the queue. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + </field> + + <field name = "queue" domain = "queue-name"> + <doc> + Specifies the name of the queue to consume from. If the queue name is null, + refers to the current queue for the channel, which is the last declared queue. + </doc> + <rule name = "01"> + <doc> + If the client did not previously declare a queue, and the queue name in this + method is empty, the server MUST raise a connection exception with reply + code 530 (not allowed). + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + </field> + + <field name = "no-ack" domain = "no-ack" /> + </method> + + <method name = "get-ok" synchronous = "1" content = "1" index = "71" + label = "provide client with a message"> + <doc> + This method delivers a message to the client following a get method. A message + delivered by 'get-ok' must be acknowledged unless the no-ack option was set in the + get method. + </doc> + + <chassis name = "client" implement = "MAY" /> + + <field name = "delivery-tag" domain = "delivery-tag" /> + + <field name = "redelivered" domain = "redelivered" /> + + <field name = "exchange" domain = "exchange-name"> + <doc> + Specifies the name of the exchange that the message was originally published to. + If empty, the message was published to the default exchange. + </doc> + </field> + + <field name = "routing-key" domain = "shortstr" label = "Message routing key"> + <doc>Specifies the routing key name specified when the message was published.</doc> + </field> + + <field name = "message-count" domain = "long" label = "number of messages pending"> + <doc> + This field reports the number of messages pending on the queue, excluding the + message being delivered. Note that this figure is indicative, not reliable, and + can change arbitrarily as messages are added to the queue and removed by other + clients. + </doc> + </field> + </method> + + <method name = "get-empty" synchronous = "1" index = "72" + label = "indicate no messages available"> + <doc> + This method tells the client that the queue has no messages available for the + client. + </doc> + + <chassis name = "client" implement = "MAY" /> + + <!-- This field is deprecated pending review --> + <field name = "cluster-id" domain = "shortstr" label = "Cluster id"> + <doc> + For use by cluster applications, should not be used by client applications. + </doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "ack" index = "80" label = "acknowledge one or more messages"> + <doc> + This method acknowledges one or more messages delivered via the Deliver or Get-Ok + methods. The client can ask to confirm a single message or a set of messages up to + and including a specific message. + </doc> + + <chassis name = "server" implement = "MUST" /> + + <field name = "delivery-tag" domain = "delivery-tag" /> + + <field name = "multiple" domain = "bit" label = "acknowledge multiple messages"> + <doc> + If set to 1, the delivery tag is treated as "up to and including", so that the + client can acknowledge multiple messages with a single method. If set to zero, + the delivery tag refers to a single message. If the multiple field is 1, and the + delivery tag is zero, tells the server to acknowledge all outstanding mesages. + </doc> + + <!-- Rule test name: was "amq_basic_20" --> + <rule name = "01"> + <doc> + The server MUST validate that a non-zero delivery-tag refers to an delivered + message, and raise a channel exception if this is not the case. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "reject" index = "90" label = "reject an incoming message"> + <doc> + This method allows a client to reject a message. It can be used to interrupt and + cancel large incoming messages, or return untreatable messages to their original + queue. + </doc> + + <!-- Rule test name: was "amq_basic_21" --> + <rule name = "01"> + <doc> + The server SHOULD be capable of accepting and process the Reject method while + sending message content with a Deliver or Get-Ok method. I.e. the server should + read and process incoming methods while sending output frames. To cancel a + partially-send content, the server sends a content body frame of size 1 (i.e. + with no data except the frame-end octet). + </doc> + </rule> + + <!-- Rule test name: was "amq_basic_22" --> + <rule name = "02"> + <doc> + The server SHOULD interpret this method as meaning that the client is unable to + process the message at this time. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + + <rule name = "03"> + <!-- TODO: Rule split? --> + <doc> + A client MUST NOT use this method as a means of selecting messages to process. A + rejected message MAY be discarded or dead-lettered, not necessarily passed to + another client. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + + <field name = "delivery-tag" domain = "delivery-tag" /> + + <field name = "requeue" domain = "bit" label = "requeue the message"> + <doc> + If this field is zero, the message will be discarded. If this bit is 1, the + server will attempt to requeue the message. + </doc> + + <!-- Rule test name: was "amq_basic_23" --> + <rule name = "01"> + <!-- TODO: Rule split? --> + <doc> + The server MUST NOT deliver the message to the same client within the + context of the current channel. The recommended strategy is to attempt to + deliver the message to an alternative consumer, and if that is not possible, + to move the message to a dead-letter queue. The server MAY use more + sophisticated tracking to hold the message on the queue and redeliver it to + the same client at a later stage. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + </field> + </method> + + <method name = "recover" index = "100" label = "redeliver unacknowledged messages"> + <doc> + This method asks the broker to redeliver all unacknowledged messages on a specified + channel. Zero or more messages may be redelivered. This method is only allowed on + non-transacted channels. + </doc> + + <rule name = "01"> + <doc> + The server MUST set the redelivered flag on all messages that are resent. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + + <rule name = "02"> + <doc> + The server MUST raise a channel exception if this is called on a transacted + channel. + </doc> + <doc type = "scenario"> + TODO. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + + <field name = "requeue" domain = "bit" label = "requeue the message"> + <doc> + If this field is zero, the message will be redelivered to the original + recipient. If this bit is 1, the server will attempt to requeue the message, + potentially then delivering it to an alternative subscriber. + </doc> + </field> + </method> + </class> + + <!-- == FILE ============================================================= --> + + <class name = "file" handler = "channel" index = "70" label = "work with file content"> + <doc> + The file class provides methods that support reliable file transfer. File + messages have a specific set of properties that are required for interoperability + with file transfer applications. File messages and acknowledgements are subject to + channel transactions. Note that the file class does not provide message browsing + methods; these are not compatible with the staging model. Applications that need + browsable file transfer should use Basic content and the Basic class. + </doc> + + <doc type = "grammar"> + file = C:QOS S:QOS-OK + / C:CONSUME S:CONSUME-OK + / C:CANCEL S:CANCEL-OK + / C:OPEN S:OPEN-OK C:STAGE content + / S:OPEN C:OPEN-OK S:STAGE content + / C:PUBLISH + / S:DELIVER + / S:RETURN + / C:ACK + / C:REJECT + </doc> + + <chassis name = "server" implement = "MAY" /> + <chassis name = "client" implement = "MAY" /> + + <rule name = "01"> + <doc> + The server MUST make a best-effort to hold file messages on a reliable storage + mechanism. + </doc> + </rule> + + <!-- TODO Rule implement attr inverse? --> + + <!-- TODO: Rule split? --> + + <rule name = "02"> + <doc> + The server MUST NOT discard a file message in case of a queue overflow. The server + MUST use the Channel.Flow method to slow or stop a file message publisher when + necessary. + </doc> + </rule> + + <!-- TODO: Rule split? --> + + <rule name = "03"> + <doc> + The server MUST implement at least 2 priority levels for file messages, where + priorities 0-4 and 5-9 are treated as two distinct levels. The server MAY implement + up to 10 priority levels. + </doc> + </rule> + + <rule name = "04"> + <doc> + The server MUST support both automatic and explicit acknowledgements on file + content. + </doc> + </rule> + + <!-- These are the properties for a File content --> + + <field name = "content-type" domain = "shortstr" label = "MIME content type" /> + <field name = "content-encoding" domain = "shortstr" label = "MIME content encoding" /> + <field name = "headers" domain = "table" label = "message header field table" /> + <field name = "priority" domain = "octet" label = "message priority, 0 to 9" /> + <field name = "reply-to" domain = "shortstr" label = "destination to reply to" /> + <field name = "message-id" domain = "shortstr" label = "application message identifier" /> + <field name = "filename" domain = "shortstr" label = "message filename" /> + <field name = "timestamp" domain = "timestamp" label = "message timestamp" /> + <!-- This field is deprecated pending review --> + <field name = "cluster-id" domain = "shortstr" label = "intra-cluster routing identifier" /> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "qos" synchronous = "1" index = "10" label = "specify quality of service"> + <doc> + This method requests a specific quality of service. The QoS can be specified for the + current channel or for all channels on the connection. The particular properties and + semantics of a qos method always depend on the content class semantics. Though the + qos method could in principle apply to both peers, it is currently meaningful only + for the server. + </doc> + + <chassis name = "server" implement = "MUST" /> + + <response name = "qos-ok" /> + + <field name = "prefetch-size" domain = "long" label = "prefetch window in octets"> + <doc> + The client can request that messages be sent in advance so that when the client + finishes processing a message, the following message is already held locally, + rather than needing to be sent down the channel. Prefetching gives a performance + improvement. This field specifies the prefetch window size in octets. May be set + to zero, meaning "no specific limit". Note that other prefetch limits may still + apply. The prefetch-size is ignored if the no-ack option is set. + </doc> + </field> + + <field name = "prefetch-count" domain = "short" label = "prefetch window in messages"> + <doc> + Specifies a prefetch window in terms of whole messages. This is compatible with + some file API implementations. This field may be used in combination with the + prefetch-size field; a message will only be sent in advance if both prefetch + windows (and those at the channel and connection level) allow it. The + prefetch-count is ignored if the no-ack option is set. + </doc> + + <rule name = "01"> + <!-- TODO: Rule split? --> + <doc> + The server MAY send less data in advance than allowed by the client's + specified prefetch windows but it MUST NOT send more. + </doc> + </rule> + </field> + + <field name = "global" domain = "bit" label = "apply to entire connection"> + <doc> + By default the QoS settings apply to the current channel only. If this field is + set, they are applied to the entire connection. + </doc> + </field> + </method> + + <method name = "qos-ok" synchronous = "1" index = "11" label = "confirm the requested qos"> + <doc> + This method tells the client that the requested QoS levels could be handled by the + server. The requested QoS applies to all active consumers until a new QoS is + defined. + </doc> + + <chassis name = "client" implement = "MUST" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "consume" synchronous = "1" index = "20" label = "start a queue consumer"> + <doc> + This method asks the server to start a "consumer", which is a transient request for + messages from a specific queue. Consumers last as long as the channel they were + created on, or until the client cancels them. + </doc> + + <rule name = "01"> + <doc> + The server SHOULD support at least 16 consumers per queue, unless the queue was + declared as private, and ideally, impose no limit except as defined by available + resources. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + + <response name = "consume-ok" /> + + <field name = "ticket" domain = "access-ticket"> + <rule name = "01"> + <doc> + The client MUST provide a valid access ticket giving "read" access rights to + the realm for the queue. + </doc> + </rule> + </field> + + <field name = "queue" domain = "queue-name"> + <doc> + Specifies the name of the queue to consume from. If the queue name is null, + refers to the current queue for the channel, which is the last declared queue. + </doc> + + <rule name = "01"> + <doc> + If the client did not previously declare a queue, and the queue name in this + method is empty, the server MUST raise a connection exception with reply + code 530 (not allowed). + </doc> + </rule> + </field> + + <field name = "consumer-tag" domain = "consumer-tag"> + <doc> + Specifies the identifier for the consumer. The consumer tag is local to a + connection, so two clients can use the same consumer tags. If this field is + empty the server will generate a unique tag. + </doc> + + <rule name = "01"> + <!-- TODO: Rule split? --> + <doc> + The tag MUST NOT refer to an existing consumer. If the client attempts to + create two consumers with the same non-empty tag the server MUST raise a + connection exception with reply code 530 (not allowed). + </doc> + </rule> + </field> + + <field name = "no-local" domain = "no-local" /> + + <field name = "no-ack" domain = "no-ack" /> + + <field name = "exclusive" domain = "bit" label = "request exclusive access"> + <doc> + Request exclusive consumer access, meaning only this consumer can access the + queue. + </doc> + + <!-- Rule test name: was "amq_file_00" --> + <rule name = "01"> + <doc> + If the server cannot grant exclusive access to the queue when asked, - + because there are other consumers active - it MUST raise a channel exception + with return code 405 (resource locked). + </doc> + </rule> + </field> + + <field name = "nowait" domain = "bit" label = "do not send a reply method"> + <doc> + If set, the server will not respond to the method. The client should not wait + for a reply method. If the server could not complete the method it will raise a + channel or connection exception. + </doc> + </field> + </method> + + <method name = "consume-ok" synchronous = "1" index = "21" label = "confirm a new consumer"> + <doc> + This method provides the client with a consumer tag which it MUST use in methods + that work with the consumer. + </doc> + + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer-tag" domain = "consumer-tag"> + <doc>Holds the consumer tag specified by the client or provided by the server.</doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "cancel" synchronous = "1" index = "30" label = "end a queue consumer"> + <doc> + This method cancels a consumer. This does not affect already delivered messages, but + it does mean the server will not send any more messages for that consumer. + </doc> + + <response name = "cancel-ok" /> + + <chassis name = "server" implement = "MUST" /> + + <field name = "consumer-tag" domain = "consumer-tag" /> + + <field name = "nowait" domain = "bit" label = "do not send a reply method"> + <doc> + If set, the server will not respond to the method. The client should not wait + for a reply method. If the server could not complete the method it will raise a + channel or connection exception. + </doc> + </field> + </method> + + <method name = "cancel-ok" synchronous = "1" index = "31" label = "confirm a cancelled consumer"> + <doc>This method confirms that the cancellation was completed.</doc> + + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer-tag" domain = "consumer-tag" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "open" synchronous = "1" index = "40" label = "request to start staging"> + <doc> + This method requests permission to start staging a message. Staging means sending + the message into a temporary area at the recipient end and then delivering the + message by referring to this temporary area. Staging is how the protocol handles + partial file transfers - if a message is partially staged and the connection breaks, + the next time the sender starts to stage it, it can restart from where it left off. + </doc> + + <response name = "open-ok" /> + + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> + + <field name = "identifier" domain = "shortstr" label = "staging identifier"> + <doc> + This is the staging identifier. This is an arbitrary string chosen by the + sender. For staging to work correctly the sender must use the same staging + identifier when staging the same message a second time after recovery from a + failure. A good choice for the staging identifier would be the SHA1 hash of the + message properties data (including the original filename, revised time, etc.). + </doc> + </field> + + <field name = "content-size" domain = "longlong" label = "message content size"> + <doc> + The size of the content in octets. The recipient may use this information to + allocate or check available space in advance, to avoid "disk full" errors during + staging of very large messages. + </doc> + + <rule name = "01"> + <doc> + The sender MUST accurately fill the content-size field. Zero-length content + is permitted. + </doc> + </rule> + </field> + </method> + + <method name = "open-ok" synchronous = "1" index = "41" label = "confirm staging ready"> + <doc> + This method confirms that the recipient is ready to accept staged data. If the + message was already partially-staged at a previous time the recipient will report + the number of octets already staged. + </doc> + + <response name = "stage" /> + + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> + + <field name = "staged-size" domain = "longlong" label = "already staged amount"> + <doc> + The amount of previously-staged content in octets. For a new message this will + be zero. + </doc> + + <rule name = "01"> + <doc> + The sender MUST start sending data from this octet offset in the message, + counting from zero. + </doc> + </rule> + + <rule name = "02"> + <!-- TODO: Rule split? --> + <doc> + The recipient MAY decide how long to hold partially-staged content and MAY + implement staging by always discarding partially-staged content. However if + it uses the file content type it MUST support the staging methods. + </doc> + </rule> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "stage" content = "1" index = "50" label = "stage message content"> + <doc> + This method stages the message, sending the message content to the recipient from + the octet offset specified in the Open-Ok method. + </doc> + + <chassis name = "server" implement = "MUST" /> + <chassis name = "client" implement = "MUST" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "publish" index = "60" label = "publish a message"> + <doc> + This method publishes a staged file message to a specific exchange. The file message + will be routed to queues as defined by the exchange configuration and distributed to + any active consumers when the transaction, if any, is committed. + </doc> + + <chassis name = "server" implement = "MUST" /> + + <field name = "ticket" domain = "access-ticket"> + <rule name = "01"> + <doc> + The client MUST provide a valid access ticket giving "write" access rights + to the access realm for the exchange. + </doc> + </rule> + </field> + + <field name = "exchange" domain = "exchange-name"> + <doc> + Specifies the name of the exchange to publish to. The exchange name can be + empty, meaning the default exchange. If the exchange name is specified, and that + exchange does not exist, the server will raise a channel exception. + </doc> + + <rule name = "01"> + <doc> + The server MUST accept a blank exchange name to mean the default exchange. + </doc> + </rule> + + <rule name = "02"> + <doc> + If the exchange was declared as an internal exchange, the server MUST + respond with a reply code 403 (access refused) and raise a channel + exception. + </doc> + </rule> + + <!-- TODO: Rule split? --> + + <rule name = "03"> + <doc> + The exchange MAY refuse file content in which case it MUST respond with a + reply code 540 (not implemented) and raise a channel exception. + </doc> + </rule> + </field> + + <field name = "routing-key" domain = "shortstr" label = "Message routing key"> + <doc> + Specifies the routing key for the message. The routing key is used for routing + messages depending on the exchange configuration. + </doc> + </field> + + <field name = "mandatory" domain = "bit" label = "indicate mandatory routing"> + <doc> + This flag tells the server how to react if the message cannot be routed to a + queue. If this flag is set, the server will return an unroutable message with a + Return method. If this flag is zero, the server silently drops the message. + </doc> + + <!-- Rule test name: was "amq_file_00" --> + <rule name = "01"> + <doc>The server SHOULD implement the mandatory flag.</doc> + </rule> + </field> + + <field name = "immediate" domain = "bit" label = "request immediate delivery"> + <doc> + This flag tells the server how to react if the message cannot be routed to a + queue consumer immediately. If this flag is set, the server will return an + undeliverable message with a Return method. If this flag is zero, the server + will queue the message, but with no guarantee that it will ever be consumed. + </doc> + + <!-- Rule test name: was "amq_file_00" --> + <rule name = "01"> + <doc>The server SHOULD implement the immediate flag.</doc> + </rule> + </field> + + <field name = "identifier" domain = "shortstr" label = "staging identifier"> + <doc> + This is the staging identifier of the message to publish. The message must have + been staged. Note that a client can send the Publish method asynchronously + without waiting for staging to finish. + </doc> + </field> + </method> + + <method name = "return" content = "1" index = "70" label = "return a failed message"> + <doc> + This method returns an undeliverable message that was published with the "immediate" + flag set, or an unroutable message published with the "mandatory" flag set. The + reply code and text provide information about the reason that the message was + undeliverable. + </doc> + + <chassis name = "client" implement = "MUST" /> + + <field name = "reply-code" domain = "reply-code" /> + + <field name = "reply-text" domain = "reply-text" /> + + <field name = "exchange" domain = "exchange-name"> + <doc> + Specifies the name of the exchange that the message was originally published to. + </doc> + </field> + + <field name = "routing-key" domain = "shortstr" label = "Message routing key"> + <doc>Specifies the routing key name specified when the message was published.</doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "deliver" index = "80" label = "notify the client of a consumer message"> + <doc> + This method delivers a staged file message to the client, via a consumer. In the + asynchronous message delivery model, the client starts a consumer using the Consume + method, then the server responds with Deliver methods as and when messages arrive + for that consumer. + </doc> + + <rule name = "01"> + <!-- TODO: Rule split? --> + <doc> + The server SHOULD track the number of times a message has been delivered to + clients and when a message is redelivered a certain number of times - e.g. 5 + times - without being acknowledged, the server SHOULD consider the message to be + unprocessable (possibly causing client applications to abort), and move the + message to a dead letter queue. + </doc> + </rule> + + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer-tag" domain = "consumer-tag" /> + + <field name = "delivery-tag" domain = "delivery-tag" /> + + <field name = "redelivered" domain = "redelivered" /> + + <field name = "exchange" domain = "exchange-name"> + <doc> + Specifies the name of the exchange that the message was originally published to. + </doc> + </field> + + <field name = "routing-key" domain = "shortstr" label = "Message routing key"> + <doc>Specifies the routing key name specified when the message was published.</doc> + </field> + + <field name = "identifier" domain = "shortstr" label = "staging identifier"> + <doc> + This is the staging identifier of the message to deliver. The message must have + been staged. Note that a server can send the Deliver method asynchronously + without waiting for staging to finish. + </doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "ack" index = "90" label = "acknowledge one or more messages"> + <doc> + This method acknowledges one or more messages delivered via the Deliver method. The + client can ask to confirm a single message or a set of messages up to and including + a specific message. + </doc> + + <chassis name = "server" implement = "MUST" /> + + <field name = "delivery-tag" domain = "delivery-tag" /> + + <field name = "multiple" domain = "bit" label = "acknowledge multiple messages"> + <doc> + If set to 1, the delivery tag is treated as "up to and including", so that the + client can acknowledge multiple messages with a single method. If set to zero, + the delivery tag refers to a single message. If the multiple field is 1, and the + delivery tag is zero, tells the server to acknowledge all outstanding mesages. + </doc> + + <rule name = "01"> + <doc> + The server MUST validate that a non-zero delivery-tag refers to an delivered + message, and raise a channel exception if this is not the case. + </doc> + </rule> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "reject" index = "100" label = "reject an incoming message"> + <doc> + This method allows a client to reject a message. It can be used to return + untreatable messages to their original queue. Note that file content is staged + before delivery, so the client will not use this method to interrupt delivery of a + large message. + </doc> + + <rule name = "01"> + <doc> + The server SHOULD interpret this method as meaning that the client is unable to + process the message at this time. + </doc> + </rule> + + <!-- TODO: Rule split? --> + + <rule name = "02"> + <doc> + A client MUST NOT use this method as a means of selecting messages to process. A + rejected message MAY be discarded or dead-lettered, not necessarily passed to + another client. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + + <field name = "delivery-tag" domain = "delivery-tag" /> + + <field name = "requeue" domain = "bit" label = "requeue the message"> + <doc> + If this field is zero, the message will be discarded. If this bit is 1, the + server will attempt to requeue the message. + </doc> + + <rule name = "01"> + <!-- TODO: Rule split? --> + <doc> + The server MUST NOT deliver the message to the same client within the + context of the current channel. The recommended strategy is to attempt to + deliver the message to an alternative consumer, and if that is not possible, + to move the message to a dead-letter queue. The server MAY use more + sophisticated tracking to hold the message on the queue and redeliver it to + the same client at a later stage. + </doc> + </rule> + </field> + </method> + </class> + + <!-- == STREAM =========================================================== --> + + <class name = "stream" handler = "channel" index = "80" label = "work with streaming content"> + <doc> + The stream class provides methods that support multimedia streaming. The stream class + uses the following semantics: one message is one packet of data; delivery is + unacknowleged and unreliable; the consumer can specify quality of service parameters + that the server can try to adhere to; lower-priority messages may be discarded in favour + of high priority messages. + </doc> + + <doc type = "grammar"> + stream = C:QOS S:QOS-OK + / C:CONSUME S:CONSUME-OK + / C:CANCEL S:CANCEL-OK + / C:PUBLISH content + / S:RETURN + / S:DELIVER content + </doc> + + <chassis name = "server" implement = "MAY" /> + <chassis name = "client" implement = "MAY" /> + + <rule name = "01"> + <doc> + The server SHOULD discard stream messages on a priority basis if the queue size + exceeds some configured limit. + </doc> + </rule> + + <rule name = "02"> + <!-- TODO: Rule split? --> + <doc> + The server MUST implement at least 2 priority levels for stream messages, where + priorities 0-4 and 5-9 are treated as two distinct levels. The server MAY implement + up to 10 priority levels. + </doc> + </rule> + + <rule name = "03"> + <doc> + The server MUST implement automatic acknowledgements on stream content. That is, as + soon as a message is delivered to a client via a Deliver method, the server must + remove it from the queue. + </doc> + </rule> + + <!-- These are the properties for a Stream content --> + + <field name = "content-type" domain = "shortstr" label = "MIME content type" /> + <field name = "content-encoding" domain = "shortstr" label = "MIME content encoding" /> + <field name = "headers" domain = "table" label = "message header field table" /> + <field name = "priority" domain = "octet" label = "message priority, 0 to 9" /> + <field name = "timestamp" domain = "timestamp" label = "message timestamp" /> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "qos" synchronous = "1" index = "10" label = "specify quality of service"> + <doc> + This method requests a specific quality of service. The QoS can be specified for the + current channel or for all channels on the connection. The particular properties and + semantics of a qos method always depend on the content class semantics. Though the + qos method could in principle apply to both peers, it is currently meaningful only + for the server. + </doc> + + <chassis name = "server" implement = "MUST" /> + + <response name = "qos-ok" /> + + <field name = "prefetch-size" domain = "long" label = "prefetch window in octets"> + <doc> + The client can request that messages be sent in advance so that when the client + finishes processing a message, the following message is already held locally, + rather than needing to be sent down the channel. Prefetching gives a performance + improvement. This field specifies the prefetch window size in octets. May be set + to zero, meaning "no specific limit". Note that other prefetch limits may still + apply. + </doc> + </field> + + <field name = "prefetch-count" domain = "short" label = "prefetch window in messages"> + <doc> + Specifies a prefetch window in terms of whole messages. This field may be used + in combination with the prefetch-size field; a message will only be sent in + advance if both prefetch windows (and those at the channel and connection level) + allow it. + </doc> + </field> + + <field name = "consume-rate" domain = "long" label = "transfer rate in octets/second"> + <doc> + Specifies a desired transfer rate in octets per second. This is usually + determined by the application that uses the streaming data. A value of zero + means "no limit", i.e. as rapidly as possible. + </doc> + + <rule name = "01"> + <!-- TODO: Rule split? --> + <doc> + The server MAY ignore the prefetch values and consume rates, depending on + the type of stream and the ability of the server to queue and/or reply it. + The server MAY drop low-priority messages in favour of high-priority + messages. + </doc> + </rule> + </field> + + <field name = "global" domain = "bit" label = "apply to entire connection"> + <doc> + By default the QoS settings apply to the current channel only. If this field is + set, they are applied to the entire connection. + </doc> + </field> + </method> + + <method name = "qos-ok" synchronous = "1" index = "11" label = "confirm the requested qos"> + <doc> + This method tells the client that the requested QoS levels could be handled by the + server. The requested QoS applies to all active consumers until a new QoS is + defined. + </doc> + + <chassis name = "client" implement = "MUST" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "consume" synchronous = "1" index = "20" label = "start a queue consumer"> + <doc> + This method asks the server to start a "consumer", which is a transient request for + messages from a specific queue. Consumers last as long as the channel they were + created on, or until the client cancels them. + </doc> + + <rule name = "01"> + <doc> + The server SHOULD support at least 16 consumers per queue, unless the queue was + declared as private, and ideally, impose no limit except as defined by available + resources. + </doc> + </rule> + + <rule name = "02"> + <doc> + Streaming applications SHOULD use different channels to select different + streaming resolutions. AMQP makes no provision for filtering and/or transforming + streams except on the basis of priority-based selective delivery of individual + messages. + </doc> + </rule> + + <chassis name = "server" implement = "MUST" /> + <response name = "consume-ok" /> + + <field name = "ticket" domain = "access-ticket"> + <rule name = "01"> + <doc> + The client MUST provide a valid access ticket giving "read" access rights to + the realm for the queue. + </doc> + </rule> + </field> + + <field name = "queue" domain = "queue-name"> + <doc> + Specifies the name of the queue to consume from. If the queue name is null, + refers to the current queue for the channel, which is the last declared queue. + </doc> + + <rule name = "01"> + <doc> + If the client did not previously declare a queue, and the queue name in this + method is empty, the server MUST raise a connection exception with reply + code 530 (not allowed). + </doc> + </rule> + </field> + + <field name = "consumer-tag" domain = "consumer-tag"> + <doc> + Specifies the identifier for the consumer. The consumer tag is local to a + connection, so two clients can use the same consumer tags. If this field is + empty the server will generate a unique tag. + </doc> + + <rule name = "01"> + <!-- TODO: Rule split? --> + <doc> + The tag MUST NOT refer to an existing consumer. If the client attempts to + create two consumers with the same non-empty tag the server MUST raise a + connection exception with reply code 530 (not allowed). + </doc> + </rule> + </field> + + <field name = "no-local" domain = "no-local" /> + + <field name = "exclusive" domain = "bit" label = "request exclusive access"> + <doc> + Request exclusive consumer access, meaning only this consumer can access the + queue. + </doc> + + + <!-- Rule test name: was "amq_file_00" --> + <rule name = "01"> + <doc> + If the server cannot grant exclusive access to the queue when asked, - + because there are other consumers active - it MUST raise a channel exception + with return code 405 (resource locked). + </doc> + </rule> + </field> + + <field name = "nowait" domain = "bit" label = "do not send a reply method"> + <doc> + If set, the server will not respond to the method. The client should not wait + for a reply method. If the server could not complete the method it will raise a + channel or connection exception. + </doc> + </field> + </method> + + <method name = "consume-ok" synchronous = "1" index = "21" label = "confirm a new consumer"> + <doc> + This method provides the client with a consumer tag which it may use in methods that + work with the consumer. + </doc> + + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer-tag" domain = "consumer-tag"> + <doc>Holds the consumer tag specified by the client or provided by the server.</doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "cancel" synchronous = "1" index = "30" label = "end a queue consumer"> + <doc> + This method cancels a consumer. Since message delivery is asynchronous the client + may continue to receive messages for a short while after canceling a consumer. It + may process or discard these as appropriate. + </doc> + + <chassis name = "server" implement = "MUST" /> + + <response name = "cancel-ok" /> + + <field name = "consumer-tag" domain = "consumer-tag" /> + + <field name = "nowait" domain = "bit" label = "do not send a reply method"> + <doc> + If set, the server will not respond to the method. The client should not wait + for a reply method. If the server could not complete the method it will raise a + channel or connection exception. + </doc> + </field> + </method> + + <method name = "cancel-ok" synchronous = "1" index = "31" label = "confirm a cancelled consumer"> + <doc>This method confirms that the cancellation was completed.</doc> + + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer-tag" domain = "consumer-tag" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "publish" content = "1" index = "40" label = "publish a message"> + <doc> + This method publishes a message to a specific exchange. The message will be routed + to queues as defined by the exchange configuration and distributed to any active + consumers as appropriate. + </doc> + + <chassis name = "server" implement = "MUST" /> + + <field name = "ticket" domain = "access-ticket"> + <rule name = "01"> + <doc> + The client MUST provide a valid access ticket giving "write" access rights + to the access realm for the exchange. + </doc> + </rule> + </field> + + <field name = "exchange" domain = "exchange-name"> + <doc> + Specifies the name of the exchange to publish to. The exchange name can be + empty, meaning the default exchange. If the exchange name is specified, and that + exchange does not exist, the server will raise a channel exception. + </doc> + + <rule name = "01"> + <doc> + The server MUST accept a blank exchange name to mean the default exchange. + </doc> + </rule> + + <rule name = "02"> + <doc> + If the exchange was declared as an internal exchange, the server MUST + respond with a reply code 403 (access refused) and raise a channel + exception. + </doc> + </rule> + + <rule name = "03"> + <doc> + The exchange MAY refuse stream content in which case it MUST respond with a + reply code 540 (not implemented) and raise a channel exception. + </doc> + </rule> + </field> + + <field name = "routing-key" domain = "shortstr" label = "Message routing key"> + <doc> + Specifies the routing key for the message. The routing key is used for routing + messages depending on the exchange configuration. + </doc> + </field> + + <field name = "mandatory" domain = "bit" label = "indicate mandatory routing"> + <doc> + This flag tells the server how to react if the message cannot be routed to a + queue. If this flag is set, the server will return an unroutable message with a + Return method. If this flag is zero, the server silently drops the message. + </doc> + + <!-- Rule test name: was "amq_stream_00" --> + <rule name = "01"> + <doc>The server SHOULD implement the mandatory flag.</doc> + </rule> + </field> + + <field name = "immediate" domain = "bit" label = "request immediate delivery"> + <doc> + This flag tells the server how to react if the message cannot be routed to a + queue consumer immediately. If this flag is set, the server will return an + undeliverable message with a Return method. If this flag is zero, the server + will queue the message, but with no guarantee that it will ever be consumed. + </doc> + + <!-- Rule test name: was "amq_stream_00" --> + <rule name = "01"> + <doc>The server SHOULD implement the immediate flag.</doc> + </rule> + </field> + </method> + + <method name = "return" content = "1" index = "50" label = "return a failed message"> + <doc> + This method returns an undeliverable message that was published with the "immediate" + flag set, or an unroutable message published with the "mandatory" flag set. The + reply code and text provide information about the reason that the message was + undeliverable. + </doc> + + <chassis name = "client" implement = "MUST" /> + + <field name = "reply-code" domain = "reply-code" /> + + <field name = "reply-text" domain = "reply-text" /> + + <field name = "exchange" domain = "exchange-name"> + <doc> + Specifies the name of the exchange that the message was originally published to. + </doc> + </field> + + <field name = "routing-key" domain = "shortstr" label = "Message routing key"> + <doc>Specifies the routing key name specified when the message was published.</doc> + </field> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "deliver" content = "1" index = "60" + label = "notify the client of a consumer message"> + <doc> + This method delivers a message to the client, via a consumer. In the asynchronous + message delivery model, the client starts a consumer using the Consume method, then + the server responds with Deliver methods as and when messages arrive for that + consumer. + </doc> + + <chassis name = "client" implement = "MUST" /> + + <field name = "consumer-tag" domain = "consumer-tag" /> + + <field name = "delivery-tag" domain = "delivery-tag" /> + + <field name = "exchange" domain = "exchange-name"> + <doc> + Specifies the name of the exchange that the message was originally published to. + </doc> + </field> + + <field name = "queue" domain = "queue-name"> + <doc> + Specifies the name of the queue that the message came from. Note that a single + channel can start many consumers on different queues. + </doc> + <assert check = "notnull" /> + </field> + </method> + </class> + + <!-- == TX =============================================================== --> + + <class name = "tx" handler = "channel" index = "90" label = "work with standard transactions"> + <doc> + Standard transactions provide so-called "1.5 phase commit". We can ensure that work is + never lost, but there is a chance of confirmations being lost, so that messages may be + resent. Applications that use standard transactions must be able to detect and ignore + duplicate messages. + </doc> + + <!-- TODO: Rule split? --> + + <rule name = "01"> + <doc> + An client using standard transactions SHOULD be able to track all messages received + within a reasonable period, and thus detect and reject duplicates of the same + message. It SHOULD NOT pass these to the application layer. + </doc> + </rule> + + <doc type = "grammar"> + tx = C:SELECT S:SELECT-OK + / C:COMMIT S:COMMIT-OK + / C:ROLLBACK S:ROLLBACK-OK + </doc> + + <chassis name = "server" implement = "SHOULD" /> + <chassis name = "client" implement = "MAY" /> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "select" synchronous = "1" index = "10" label = "select standard transaction mode"> + <doc> + This method sets the channel to use standard transactions. The client must use this + method at least once on a channel before using the Commit or Rollback methods. + </doc> + <chassis name = "server" implement = "MUST" /> + <response name = "select-ok" /> + </method> + + <method name = "select-ok" synchronous = "1" index = "11" label = "confirm transaction mode"> + <doc> + This method confirms to the client that the channel was successfully set to use + standard transactions. + </doc> + <chassis name = "client" implement = "MUST" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "commit" synchronous = "1" index = "20" label = "commit the current transaction"> + <doc> + This method commits all messages published and acknowledged in the current + transaction. A new transaction starts immediately after a commit. + </doc> + <chassis name = "server" implement = "MUST" /> + <response name = "commit-ok" /> + </method> + + <method name = "commit-ok" synchronous = "1" index = "21" label = "confirm a successful commit"> + <doc> + This method confirms to the client that the commit succeeded. Note that if a commit + fails, the server raises a channel exception. + </doc> + <chassis name = "client" implement = "MUST" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "rollback" synchronous = "1" index = "30" + label = "abandon the current transaction"> + <doc> + This method abandons all messages published and acknowledged in the current + transaction. A new transaction starts immediately after a rollback. + </doc> + <chassis name = "server" implement = "MUST" /> + <response name = "rollback-ok" /> + </method> + + <method name = "rollback-ok" synchronous = "1" index = "31" label = "confirm successful rollback"> + <doc> + This method confirms to the client that the rollback succeeded. Note that if an + rollback fails, the server raises a channel exception. + </doc> + <chassis name = "client" implement = "MUST" /> + </method> + </class> + + <!-- == DTX ============================================================== --> + + <class name = "dtx" handler = "channel" index = "100" label = "work with distributed transactions"> + <doc> + Distributed transactions provide so-called "2-phase commit". The AMQP distributed + transaction model supports the X-Open XA architecture and other distributed transaction + implementations. The Dtx class assumes that the server has a private communications + channel (not AMQP) to a distributed transaction coordinator. + </doc> + + <doc type = "grammar"> + dtx = C:SELECT S:SELECT-OK + C:START S:START-OK + </doc> + + <chassis name = "server" implement = "MAY" /> + <chassis name = "client" implement = "MAY" /> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "select" synchronous = "1" index = "10" label = "select standard transaction mode"> + <doc> + This method sets the channel to use distributed transactions. The client must use + this method at least once on a channel before using the Start method. + </doc> + <chassis name = "server" implement = "MUST" /> + <response name = "select-ok" /> + </method> + + <method name = "select-ok" synchronous = "1" index = "11" label = "confirm transaction mode"> + <doc> + This method confirms to the client that the channel was successfully set to use + distributed transactions. + </doc> + <chassis name = "client" implement = "MUST" /> + </method> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "start" synchronous = "1" index = "20" + label = "start a new distributed transaction"> + <doc> + This method starts a new distributed transaction. This must be the first method on a + new channel that uses the distributed transaction mode, before any methods that + publish or consume messages. + </doc> + <chassis name = "server" implement = "MAY" /> + <response name = "start-ok" /> + <field name = "dtx-identifier" domain = "shortstr" label = "transaction identifier"> + <doc> + The distributed transaction key. This identifies the transaction so that the + AMQP server can coordinate with the distributed transaction coordinator. + </doc> + <assert check = "notnull" /> + </field> + </method> + + <method name = "start-ok" synchronous = "1" index = "21" + label = "confirm the start of a new distributed transaction"> + <doc> + This method confirms to the client that the transaction started. Note that if a + start fails, the server raises a channel exception. + </doc> + <chassis name = "client" implement = "MUST" /> + </method> + </class> + + <!-- == TUNNEL =========================================================== --> + + <class name = "tunnel" handler = "tunnel" index = "110" label = "methods for protocol tunneling"> + <doc> + The tunnel methods are used to send blocks of binary data - which can be serialised AMQP + methods or other protocol frames - between AMQP peers. + </doc> + + <doc type = "grammar"> + tunnel = C:REQUEST + / S:REQUEST + </doc> + + <chassis name = "server" implement = "MAY" /> + <chassis name = "client" implement = "MAY" /> + + <field name = "headers" domain = "table" label = "message header field table" /> + <field name = "proxy-name" domain = "shortstr" label = "identity of tunnelling proxy" /> + <field name = "data-name" domain = "shortstr" label = "name or type of message being tunnelled" /> + <field name = "durable" domain = "octet" label = "message durability indicator" /> + <field name = "broadcast" domain = "octet" label = "message broadcast mode" /> + + <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <method name = "request" content = "1" index = "10" label = "sends a tunnelled method"> + <doc> + This method tunnels a block of binary data, which can be an encoded + AMQP method or other data. The binary data is sent as the content for + the Tunnel.Request method. + </doc> + <chassis name = "server" implement = "MUST" /> + <field name = "meta-data" domain = "table" label = "meta data for the tunnelled block"> + <doc> + This field table holds arbitrary meta-data that the sender needs to + pass to the recipient. + </doc> + </field> + </method> + </class> +</amqp> diff --git a/qpid/gentools/xml-src/cluster-0.9.test.xml b/qpid/gentools/xml-src/cluster-0.9.test.xml new file mode 100644 index 0000000000..142e6c9380 --- /dev/null +++ b/qpid/gentools/xml-src/cluster-0.9.test.xml @@ -0,0 +1,59 @@ +<?xml version="1.0"?> +<!-- + - + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you under the Apache License, Version 2.0 (the + - "License"); you may not use this file except in compliance + - with the License. You may obtain a copy of the License at + - + - http://www.apache.org/licenses/LICENSE-2.0 + - + - Unless required by applicable law or agreed to in writing, + - software distributed under the License is distributed on an + - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + - KIND, either express or implied. See the License for the + - specific language governing permissions and limitations + - under the License. + - + --> + +<amqp major="0" minor="9" port="5672" comment="AMQ protocol 0.80"> + +<class name = "cluster" index = "101"> + +<doc> + An extension that allows brokers to communicate in order to + provide a clustered service to clients. +</doc> + +<method name = "join" index="10"> + <field name = "broker" type = "shortstr" /> +</method> + +<method name = "membership" index="20"> + <field name = "members" type = "longstr" /> +</method> + +<method name = "synch" index="30"> +</method> + +<method name = "leave" index="40"> + <field name = "broker" type = "shortstr" /> +</method> + +<method name = "suspect" index="50"> + <field name = "broker" type = "shortstr" /> +</method> + +<method name = "ping" index="60"> + <field name = "broker" type = "shortstr" /> + <field name = "load" type = "long" /> + <field name = "response required" type = "bit" /> +</method> + +</class> + +</amqp> |