diff options
Diffstat (limited to '')
83 files changed, 12546 insertions, 0 deletions
diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/LICENSE b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/LICENSE new file mode 100644 index 0000000000..6e529a25c4 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/LICENSE @@ -0,0 +1,205 @@ +
+ 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/branches/sca-java-1.x/modules/binding-ws-axis2-jms/NOTICE b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/NOTICE new file mode 100644 index 0000000000..1325efd8bf --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/NOTICE @@ -0,0 +1,6 @@ +${pom.name}
+Copyright (c) 2005 - 2008 The Apache Software Foundation
+
+This product includes software developed by
+The Apache Software Foundation (http://www.apache.org/).
+
diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/pom.xml b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/pom.xml new file mode 100644 index 0000000000..94448fbc5e --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/pom.xml @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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>
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.tuscany.sca</groupId>
+ <artifactId>tuscany-modules</artifactId>
+ <version>1.6-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>tuscany-binding-ws-axis2-jms</artifactId>
+ <name>Apache Tuscany SCA Axis2-based WS Binding JMS Transport</name>
+
+ <repositories>
+ <repository>
+ <id>apache.ws</id>
+ <name>Apache WebServices Repository</name>
+ <url>http://ws.zones.apache.org/repository/</url>
+ <layout>legacy</layout>
+ </repository>
+ </repositories>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.tuscany.sca</groupId>
+ <artifactId>tuscany-binding-ws-axis2</artifactId>
+ <version>1.6-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-jms_1.1_spec</artifactId>
+ <version>1.1</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-jta_1.0.1B_spec</artifactId>
+ <version>1.0</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.activemq</groupId>
+ <artifactId>activemq-core</artifactId>
+ <version>5.1.0</version>
+ <scope>test</scope>
+ <exclusions>
+ <!-- We want to choose the JAF implementation ourselves -->
+ <exclusion>
+ <groupId>javax.activation</groupId>
+ <artifactId>activation</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+
+ <configuration>
+ <instructions>
+ <Bundle-Version>${tuscany.version}</Bundle-Version>
+ <Bundle-SymbolicName>org.apache.tuscany.sca.binding.ws.axis2.jms</Bundle-SymbolicName>
+ <Bundle-Description>${pom.name}</Bundle-Description>
+ <Export-Package>org.apache.tuscany.sca.binding.ws.axis2.jms*</Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/BinaryBuilder.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/BinaryBuilder.java new file mode 100644 index 0000000000..3f47fd855b --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/BinaryBuilder.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.sca.binding.ws.axis2.format; + +import java.io.IOException; +import java.io.InputStream; + +import javax.activation.DataHandler; +import javax.activation.DataSource; +import javax.xml.namespace.QName; + +import org.apache.axiom.attachments.ByteArrayDataSource; +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMFactory; +import org.apache.axis2.AxisFault; +import org.apache.axis2.context.MessageContext; +import org.apache.axis2.description.Parameter; +import org.apache.commons.io.IOUtils; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.BaseConstants; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.BaseUtils; + +/** + * Message builder for binary payloads. + * <p> + * This builder processes the input message as binary and wraps + * the data in a wrapper element. The name of the wrapper element can + * be configured as a service parameter (see {@link BaseConstants#WRAPPER_PARAM}). + * It defaults to {@link BaseConstants#DEFAULT_BINARY_WRAPPER}. + */ +public class BinaryBuilder implements DataSourceMessageBuilder { + public OMElement processDocument(DataSource dataSource, + String contentType, + MessageContext msgContext) throws AxisFault { + QName wrapperQName = BaseConstants.DEFAULT_BINARY_WRAPPER; + if (msgContext.getAxisService() != null) { + Parameter wrapperParam = msgContext.getAxisService().getParameter(BaseConstants.WRAPPER_PARAM); + if (wrapperParam != null) { + wrapperQName = BaseUtils.getQNameFromString(wrapperParam.getValue()); + } + } + OMFactory factory = OMAbstractFactory.getOMFactory(); + OMElement wrapper = factory.createOMElement(wrapperQName, null); + DataHandler dataHandler = new DataHandler(dataSource); + wrapper.addChild(factory.createOMText(dataHandler, true)); + msgContext.setDoingMTOM(true); + return wrapper; + } + + public OMElement processDocument(InputStream inputStream, + String contentType, + MessageContext msgContext) throws AxisFault { + // TODO: this could be further optimized by deferring the read operation + byte[] msgBytes; + try { + msgBytes = IOUtils.toByteArray(inputStream); + } catch (IOException ex) { + throw new AxisFault("Unable to read message payload", ex); + } + return processDocument(new ByteArrayDataSource(msgBytes), contentType, msgContext); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/BinaryFormatter.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/BinaryFormatter.java new file mode 100644 index 0000000000..1a20d61d4b --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/BinaryFormatter.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.tuscany.sca.binding.ws.axis2.format; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URL; + +import javax.activation.DataHandler; +import javax.activation.DataSource; + +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMNode; +import org.apache.axiom.om.OMOutputFormat; +import org.apache.axiom.om.OMText; +import org.apache.axis2.AxisFault; +import org.apache.axis2.context.MessageContext; +import org.apache.axis2.transport.http.util.URLTemplatingUtil; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.BaseConstants; + +public class BinaryFormatter implements MessageFormatterEx { + public byte[] getBytes(MessageContext messageContext, OMOutputFormat format) throws AxisFault { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + writeTo(messageContext, format, baos, true); + return baos.toByteArray(); + } + + private DataHandler getDataHandler(MessageContext messageContext) { + OMElement firstChild = messageContext.getEnvelope().getBody().getFirstElement(); + if (BaseConstants.DEFAULT_BINARY_WRAPPER.equals(firstChild.getQName())) { + OMNode omNode = firstChild.getFirstOMChild(); + if (omNode != null && omNode instanceof OMText) { + Object dh = ((OMText)omNode).getDataHandler(); + if (dh != null && dh instanceof DataHandler) { + return (DataHandler)dh; + } + } + } + return null; + } + + public void writeTo(MessageContext messageContext, OMOutputFormat format, + OutputStream outputStream, boolean preserve) throws AxisFault { + DataHandler dh = getDataHandler(messageContext); + if (dh != null) { + try { + ((DataHandler)dh).writeTo(outputStream); + } catch (IOException e) { + throw new AxisFault("Error serializing binary content of element : " + + BaseConstants.DEFAULT_BINARY_WRAPPER, e); + } + } + } + + public String getContentType(MessageContext messageContext, + OMOutputFormat format, String soapAction) { + DataHandler dh = getDataHandler(messageContext); + if (dh != null) { + return dh.getContentType(); + } else { + return null; + } + } + + public URL getTargetAddress(MessageContext messageContext, + OMOutputFormat format, URL targetURL) throws AxisFault { + return URLTemplatingUtil.getTemplatedURL(targetURL, messageContext, false); + } + + public String formatSOAPAction(MessageContext messageContext, + OMOutputFormat format, String soapAction) { + return null; + } + + public DataSource getDataSource(MessageContext messageContext, + OMOutputFormat format, String soapAction) throws AxisFault { + return getDataHandler(messageContext).getDataSource(); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/DataSourceMessageBuilder.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/DataSourceMessageBuilder.java new file mode 100644 index 0000000000..4a299df50d --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/DataSourceMessageBuilder.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.tuscany.sca.binding.ws.axis2.format; + +import javax.activation.DataSource; + +import org.apache.axiom.om.OMElement; +import org.apache.axis2.AxisFault; +import org.apache.axis2.builder.Builder; +import org.apache.axis2.context.MessageContext; + +/** + * Message builder able to build messages from {@link DataSource} objects. + * This interface can be optionally implemented by {@link Builder} + * implementations that support building messages from {@link DataSource} objects. + * Since by definition the data from a {@link DataSource} can be read multiple + * times, this interface can be used by message builders to avoid storing the + * message content in memory. + * <p> + * If a message builder implements this interface and the transport is able to + * provide the message payload as a data source, then the method defined by this + * interface should be preferred over the method defined by {@link Builder}. + * <p> + * Implementing this interface helps optimizing message processing with transports + * that use messaging providers that store messages in memory or on the file system. + * Examples are JMS and VFS. + * <p> + * The builder will typically expose the data source directly or indirectly through + * the returned {@link OMElement}, e.g. by adding to the tree an {@link org.apache.axiom.om.OMText} + * or {@link org.apache.axiom.om.OMDataSource} node referencing the data source. + * This means that the builder will not be able to guarantee that all streams requested + * from the data source are properly closed. Note that code accessing the returned + * {@link OMElement} can't be expected to take care of this since in many cases the fact + * that a data source is being used is completely transparent to that code. + * It is therefore the responsibility of the transport to make sure that all resources linked to + * the data source itself as well as any open stream requested from that data source are properly + * released after the message has been processed. Depending on the type of transport, there are + * three possible cases: + * <ol> + * <li>All resources allocated to the data source or streams requested from it are + * memory based. In that case the garbage collector will take care of freeing + * these resources and the transport should simply pass the data source object + * to the builder.</li> + * <li>There are operation system resources linked to the data source and open + * streams will become invalid when these resources are freed, i.e. + * it is not required that all streams be closed explicitly. + * In this case the transport only needs to take care to properly dispose of + * the data source after the message has been processed by the Axis2 engine.</li> + * <li>Requesting a stream from the data source allocates operation system resources + * (e.g. a network connection) that remain linked to the stream, i.e. all streams requested + * from the data source must be closed properly. In that case the transport should use + * {@link ManagedDataSourceFactory#create(DataSource)} to wrap the original data source + * before passing it to the builder. After the message has been processed it should + * then call {@link ManagedDataSource#destroy()} on the wrapper to close all remaining + * open streams.</li> + * </ol> + */ +public interface DataSourceMessageBuilder extends Builder { + public OMElement processDocument(DataSource dataSource, String contentType, + MessageContext messageContext) throws AxisFault; +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/ElementHelper.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/ElementHelper.java new file mode 100644 index 0000000000..c3cd09b67a --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/ElementHelper.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.sca.binding.ws.axis2.format; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.io.Writer; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMNode; +import org.apache.axiom.om.OMSourcedElement; +import org.apache.axiom.om.OMText; + +/** + * Utility class with methods to work on {@link OMElement} objects. + * <p> + * NOTICE: The code in this class will be moved to Axiom (or somewhere else). Use with care! + */ +public class ElementHelper { + private ElementHelper() {} + + /** + * Returns a stream representing the concatenation of the text nodes that are children of a + * given element. + * The stream returned by this method produces exactly the same character sequence as the + * the stream created by the following expression: + * <pre>new StringReader(element.getText())</pre> + * The difference is that the stream implementation returned by this method is guaranteed + * to have constant memory usage and is optimized for performance. + * + * @param element the element to read the text nodes from + * @param cache whether to enable caching when accessing the element + * @return a stream representing the concatenation of the text nodes + * + * @see OMElement#getText() + */ + public static Reader getTextAsStream(OMElement element, boolean cache) { + // If the element is not an OMSourcedElement and has not more than one child, then the most + // efficient way to get the Reader is to build a StringReader + if (!(element instanceof OMSourcedElement) && (!cache || element.isComplete())) { + OMNode child = element.getFirstOMChild(); + if (child == null) { + return new StringReader(""); + } else if (child.getNextOMSibling() == null) { + return new StringReader(child instanceof OMText ? ((OMText)child).getText() : ""); + } + } + // In all other cases, extract the data from the XMLStreamReader + return new TextFromElementReader(cache ? element.getXMLStreamReader() + : element.getXMLStreamReaderWithoutCaching()); + } + + /** + * Write the content of the text nodes that are children of a given element to a + * {@link Writer}. + * If <code>cache</code> is true, this method has the same effect as the following instruction: + * <pre>out.write(element.getText())</pre> + * The difference is that this method is guaranteed to have constant memory usage and is + * optimized for performance. + * + * @param element the element to read the text nodes from + * @param out the stream to write the content to + * @param cache whether to enable caching when accessing the element + * @throws XMLStreamException if an error occurs when reading from the element + * @throws IOException if an error occurs when writing to the stream + * + * @see OMElement#getText() + */ + public static void writeTextTo(OMElement element, Writer out, boolean cache) + throws XMLStreamException, IOException { + + XMLStreamReader reader = cache ? element.getXMLStreamReader() + : element.getXMLStreamReaderWithoutCaching(); + int depth = 0; + while (reader.hasNext()) { + switch (reader.next()) { + case XMLStreamReader.CHARACTERS: + case XMLStreamReader.CDATA: + if (depth == 1) { + out.write(reader.getText()); + } + break; + case XMLStreamReader.START_ELEMENT: + depth++; + break; + case XMLStreamReader.END_ELEMENT: + depth--; + } + } + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/ManagedDataSource.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/ManagedDataSource.java new file mode 100644 index 0000000000..d45b741b0a --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/ManagedDataSource.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.tuscany.sca.binding.ws.axis2.format; + +import javax.activation.DataSource; + +/** + * Managed data source. + * This type of data source keeps track of the streams that have been + * requested using {@link DataSource#getInputStream()} and allows to + * forcibly close these streams. Any existing data source can be converted + * to a managed data source using {@link ManagedDataSourceFactory#create(DataSource)}. + */ +public interface ManagedDataSource extends DataSource { + /** + * Close all streams that have been requested from this data source + * and that are not yet closed. + */ + void destroy(); +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/ManagedDataSourceFactory.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/ManagedDataSourceFactory.java new file mode 100644 index 0000000000..0c44721ac7 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/ManagedDataSourceFactory.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.sca.binding.ws.axis2.format; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import javax.activation.DataSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Utility class to create {@link ManagedDataSource} objects. + */ +public class ManagedDataSourceFactory { + private static class ManagedInputStream extends FilterInputStream { + private DataSourceManager manager; + + public ManagedInputStream(DataSourceManager manager, InputStream parent) { + super(parent); + this.manager = manager; + } + + @Override + public void close() throws IOException { + if (manager != null) { + manager.notifyStreamClosed(this); + manager = null; + } + super.close(); + } + } + + private static class DataSourceManager implements InvocationHandler { + private static final Log log = LogFactory.getLog(DataSourceManager.class); + + private static final Method getInputStreamMethod; + private static final Method destroyMethod; + + static { + try { + getInputStreamMethod = DataSource.class.getMethod("getInputStream"); + destroyMethod = ManagedDataSource.class.getMethod("destroy"); + } catch (NoSuchMethodException ex) { + throw new NoSuchMethodError(ex.getMessage()); + } + } + + private final DataSource dataSource; + private final List<ManagedInputStream> openStreams = Collections.synchronizedList( + new LinkedList<ManagedInputStream>()); + + public DataSourceManager(DataSource dataSource) { + this.dataSource = dataSource; + } + + public void notifyStreamClosed(ManagedInputStream managedInputStream) { + if (!openStreams.remove(managedInputStream)) { + throw new IllegalStateException(); + } + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + try { + if (method.equals(getInputStreamMethod)) { + InputStream in = (InputStream)method.invoke(dataSource, args); + ManagedInputStream in2 = new ManagedInputStream(this, in); + openStreams.add(in2); + return in2; + } else if (method.equals(destroyMethod)) { + while (!openStreams.isEmpty()) { + try { + openStreams.get(0).close(); + } catch (IOException ex) { + log.warn("Exception when closing open stream from managed data source", ex); + } + } + return null; + } else { + return method.invoke(dataSource, args); + } + } catch (InvocationTargetException ex) { + throw ex.getCause(); + } + } + + } + + /** + * Create a {@link ManagedDataSource} proxy for an existing data source. + * This will create a dynamic proxy implementing the same interfaces as + * the original data source. + * + * @param ds the original data source + * @return a data source proxy implementing {@link ManagedDataSource} + */ + public static ManagedDataSource create(DataSource ds) { + Class<?>[] orgIfaces = ds.getClass().getInterfaces(); + Class<?>[] ifaces = new Class[orgIfaces.length+1]; + ifaces[0] = ManagedDataSource.class; + System.arraycopy(orgIfaces, 0, ifaces, 1, orgIfaces.length); + return (ManagedDataSource)Proxy.newProxyInstance( + ManagedDataSourceFactory.class.getClassLoader(), ifaces, + new DataSourceManager(ds)); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/MessageFormatterEx.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/MessageFormatterEx.java new file mode 100644 index 0000000000..2e6b691938 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/MessageFormatterEx.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.sca.binding.ws.axis2.format; + +import javax.activation.DataSource; + +import org.apache.axiom.om.OMOutputFormat; +import org.apache.axis2.AxisFault; +import org.apache.axis2.context.MessageContext; +import org.apache.axis2.transport.MessageFormatter; + +/** + * Message formatter with extended capabilities. + * This interface adds new methods to the {@link MessageFormatter} + * interface, allowing transport to optimize data transfers. + */ +public interface MessageFormatterEx extends MessageFormatter { + /** + * Get the formatted message as a {@link DataSource} object. + * + * @param messageContext + * @param format + * @param soapAction + * @return + * @throws AxisFault + */ + DataSource getDataSource(MessageContext messageContext, OMOutputFormat format, String soapAction) throws AxisFault; +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/MessageFormatterExAdapter.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/MessageFormatterExAdapter.java new file mode 100644 index 0000000000..e93df23974 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/MessageFormatterExAdapter.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.sca.binding.ws.axis2.format; + +import java.io.OutputStream; +import java.net.URL; + +import javax.activation.DataSource; + +import org.apache.axiom.attachments.ByteArrayDataSource; +import org.apache.axiom.om.OMOutputFormat; +import org.apache.axis2.AxisFault; +import org.apache.axis2.context.MessageContext; +import org.apache.axis2.transport.MessageFormatter; + +/** + * Adapter to add the {@link MessageFormatterEx} interface to an + * existing {@link MessageFormatter}. + * It implements the {@link MessageFormatterEx#getDataSource(MessageContext, OMOutputFormat, String)} method + * using {@link MessageFormatter#getBytes(MessageContext, OMOutputFormat)} and + * {@link MessageFormatter#getContentType(MessageContext, OMOutputFormat, String)}. + */ +public class MessageFormatterExAdapter implements MessageFormatterEx { + private final MessageFormatter messageFormatter; + + public MessageFormatterExAdapter(MessageFormatter messageFormatter) { + this.messageFormatter = messageFormatter; + } + + public DataSource getDataSource(MessageContext messageContext, + OMOutputFormat format, + String soapAction) throws AxisFault { + return new ByteArrayDataSource( + getBytes(messageContext, format), + getContentType(messageContext, format, soapAction)); + } + + public String formatSOAPAction(MessageContext messageContext, + OMOutputFormat format, + String soapAction) { + return messageFormatter.formatSOAPAction(messageContext, format, soapAction); + } + + public byte[] getBytes(MessageContext messageContext, + OMOutputFormat format) throws AxisFault { + return messageFormatter.getBytes(messageContext, format); + } + + public String getContentType(MessageContext messageContext, + OMOutputFormat format, + String soapAction) { + return messageFormatter.getContentType(messageContext, format, soapAction); + } + + public URL getTargetAddress(MessageContext messageContext, + OMOutputFormat format, + URL targetURL) throws AxisFault { + return messageFormatter.getTargetAddress(messageContext, format, targetURL); + } + + public void writeTo(MessageContext messageContext, + OMOutputFormat format, + OutputStream outputStream, + boolean preserve) throws AxisFault { + messageFormatter.writeTo(messageContext, format, outputStream, preserve); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/PlainTextBuilder.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/PlainTextBuilder.java new file mode 100644 index 0000000000..c2851bac4c --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/PlainTextBuilder.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.sca.binding.ws.axis2.format; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; + +import javax.activation.DataSource; +import javax.xml.namespace.QName; + +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMFactory; +import org.apache.axiom.om.impl.llom.OMSourcedElementImpl; +import org.apache.axis2.AxisFault; +import org.apache.axis2.builder.BuilderUtil; +import org.apache.axis2.context.MessageContext; +import org.apache.axis2.description.Parameter; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.BaseConstants; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.BaseUtils; + +/** + * Message builder for plain text payloads. + * <p> + * This builder processes the input message as plain text and wraps + * the text in a wrapper element. The name of the wrapper element can + * be configured as a service parameter (see {@link BaseConstants#WRAPPER_PARAM}). + * It defaults to {@link BaseConstants#DEFAULT_TEXT_WRAPPER}. + * If the content is provided as an {@link InputStream} and the content type specifies a + * <tt>charset</tt> parameter (e.g. <tt>text/plain; charset=ISO-8859-15</tt>), + * this information is used to decode the text. + * If the content is provided as an {@link InputStream} but no <tt>charset</tt> parameter + * is specified on the content type, the default charset encoding specified by + * {@link MessageContext#DEFAULT_CHAR_SET_ENCODING} is used. + */ +public class PlainTextBuilder implements TextMessageBuilder, DataSourceMessageBuilder { + private static QName getWrapperQName(MessageContext msgContext) { + QName wrapperQName = BaseConstants.DEFAULT_TEXT_WRAPPER; + if (msgContext.getAxisService() != null) { + Parameter wrapperParam + = msgContext.getAxisService().getParameter(BaseConstants.WRAPPER_PARAM); + if (wrapperParam != null) { + wrapperQName = BaseUtils.getQNameFromString(wrapperParam.getValue()); + } + } + return wrapperQName; + } + + public OMElement processDocument(InputStream inputStream, + String contentType, + MessageContext msgContext) throws AxisFault { + + OMFactory factory = OMAbstractFactory.getOMFactory(); + String charSetEnc = BuilderUtil.getCharSetEncoding(contentType); + QName wrapperQName = getWrapperQName(msgContext); + Reader reader; + try { + reader = new InputStreamReader(inputStream, charSetEnc); + } catch (UnsupportedEncodingException ex) { + throw new AxisFault("Unsupported encoding: " + charSetEnc, ex); + } + return new OMSourcedElementImpl(wrapperQName, factory, + new WrappedTextNodeOMDataSourceFromReader(wrapperQName, reader)); + } + + public OMElement processDocument(Reader reader, + String contentType, + MessageContext msgContext) throws AxisFault { + + OMFactory factory = OMAbstractFactory.getOMFactory(); + QName wrapperQName = getWrapperQName(msgContext); + return new OMSourcedElementImpl(wrapperQName, factory, + new WrappedTextNodeOMDataSourceFromReader(wrapperQName, reader)); + } + + public OMElement processDocument(String content, + String contentType, + MessageContext msgContext) throws AxisFault { + OMFactory factory = OMAbstractFactory.getOMFactory(); + OMElement wrapper = factory.createOMElement(getWrapperQName(msgContext), null); + factory.createOMText(wrapper, content); + return wrapper; + } + + public OMElement processDocument(DataSource dataSource, + String contentType, + MessageContext msgContext) throws AxisFault { + + OMFactory factory = OMAbstractFactory.getOMFactory(); + Charset cs = Charset.forName(BuilderUtil.getCharSetEncoding(contentType)); + QName wrapperQName = getWrapperQName(msgContext); + return new OMSourcedElementImpl(wrapperQName, factory, + new WrappedTextNodeOMDataSourceFromDataSource(wrapperQName, dataSource, cs)); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/PlainTextFormatter.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/PlainTextFormatter.java new file mode 100644 index 0000000000..3dc8684471 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/PlainTextFormatter.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.format; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.net.URL; + +import javax.activation.DataSource; +import javax.xml.stream.XMLStreamException; + +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMOutputFormat; +import org.apache.axis2.AxisFault; +import org.apache.axis2.context.MessageContext; +import org.apache.axis2.transport.http.util.URLTemplatingUtil; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.BaseConstants; + +public class PlainTextFormatter implements MessageFormatterEx { + + public byte[] getBytes(MessageContext messageContext, OMOutputFormat format) throws AxisFault { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + writeTo(messageContext, format, baos, true); + return baos.toByteArray(); + } + + public void writeTo(MessageContext messageContext, OMOutputFormat format, OutputStream outputStream, boolean preserve) throws AxisFault { + OMElement textElt = messageContext.getEnvelope().getBody().getFirstElement(); + if (BaseConstants.DEFAULT_TEXT_WRAPPER.equals(textElt.getQName())) { + try { + Writer out = new OutputStreamWriter(outputStream, format.getCharSetEncoding()); + ElementHelper.writeTextTo(textElt, out, preserve); + out.flush(); + } catch (IOException e) { + throw new AxisFault("Error writing text message to stream", e); + } catch (XMLStreamException e) { + throw new AxisFault("Error extracting the text payload from the message", e); + } + } + } + + public String getContentType(MessageContext messageContext, OMOutputFormat format, String soapAction) { + String encoding = format.getCharSetEncoding(); + String contentType = "text/plain"; + + if (encoding != null) { + contentType += "; charset=" + encoding; + } + + // if soap action is there (can be there is soap response MEP is used) add it. + if ((soapAction != null) + && !"".equals(soapAction.trim()) + && !"\"\"".equals(soapAction.trim())) { + contentType = contentType + ";action=\"" + soapAction + "\";"; + } + + return contentType; + } + + public URL getTargetAddress(MessageContext msgCtxt, OMOutputFormat format, URL targetURL) throws AxisFault { + // Check whether there is a template in the URL, if so we have to replace then with data + // values and create a new target URL. + targetURL = URLTemplatingUtil.getTemplatedURL(targetURL, msgCtxt, false); + return targetURL; + } + + public String formatSOAPAction(MessageContext messageContext, OMOutputFormat format, String soapAction) { + return null; + } + + public DataSource getDataSource(MessageContext messageContext, + OMOutputFormat format, String soapAction) throws AxisFault { + return new TextFromElementDataSource( + messageContext.getEnvelope().getBody().getFirstElement(), + format.getCharSetEncoding(), + getContentType(messageContext, format, soapAction)); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextFromElementDataSource.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextFromElementDataSource.java new file mode 100644 index 0000000000..a5597eeea8 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextFromElementDataSource.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.sca.binding.ws.axis2.format; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import javax.activation.DataSource; + +import org.apache.axiom.om.OMElement; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.streams.ReaderInputStream; + +/** + * Data source that represents the text of a given {@link OMElement}. + * <p> + * The expression + * <pre>new TextFromElementDataSource(element, charset, contentType)</pre> + * produces a DataSource implementation that is equivalent to + * <pre>new ByteArrayDataSource(element.getText().getBytes(charset), contentType)</pre> + * but that is more efficient. + */ +public class TextFromElementDataSource implements DataSource { + private final OMElement element; + private final String charset; + private final String contentType; + + public TextFromElementDataSource(OMElement element, String charset, String contentType) { + this.element = element; + this.charset = charset; + this.contentType = contentType; + } + + public String getContentType() { + return contentType; + } + + public String getName() { + return null; + } + + public InputStream getInputStream() throws IOException { + return new ReaderInputStream(ElementHelper.getTextAsStream(element, true), charset); + } + + public OutputStream getOutputStream() throws IOException { + throw new UnsupportedOperationException(); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextFromElementReader.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextFromElementReader.java new file mode 100644 index 0000000000..e4f0931bfc --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextFromElementReader.java @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.format; + +import java.io.IOException; +import java.io.Reader; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import org.apache.axiom.om.OMException; + +/** + * {@link Reader} implementation that extracts the text nodes from an element given by an + * {@link XMLStreamReader}. The expected input is a document with only a document + * element (as produced by {@link org.apache.axiom.om.OMElement.OMElement#getXMLStreamReader()}). + * The class will extract the text nodes that are direct children of that element, i.e. it uses + * the same conventions as {@link org.apache.axiom.om.OMElement.OMElement#getText()}. + * It will call {@link XMLStreamReader#close()} when the end of the document is reached or when + * {@link #close()} is called. + * <p> + * The main purpose of this class is to provide a convenient and efficient way to get the text + * content of an element without converting it first to a string, i.e. without using + * {@link org.apache.axiom.om.OMElement.OMElement#getText()}. This is important for potentially + * large contents, for which this class guarantees constant memory usage. + * <p> + * Note that this class should in general not be used directly. Instead, + * {@link ElementHelper#getTextAsStream(org.apache.axiom.om.OMElement)} + * should be called to get the most efficient stream implementation for a given an element. + * <p> + * NOTICE: The code in this class will be moved to Axiom (or somewhere else). Use with care! + */ +public class TextFromElementReader extends Reader { + private final XMLStreamReader stream; + + /** + * Flag indicating that we have reached the end of the document and that the underlying + * parser has been closed. + */ + private boolean endOfStream; + + /** + * The current depth relative to the document element (not the document). A value greater than + * 0 indicates that we are inside a nested element and that we need to skip text nodes. + */ + private int skipDepth; + + /** + * The current position in the character data of the event, or -1 if all the character data + * has been consumed and a new event needs to be requested from the parser. + */ + private int sourceStart = -1; + + /** + * Constructor. + * + * @param stream the stream to extract the text nodes from + * @throws OMException if the stream doesn't start with the expected events + */ + public TextFromElementReader(XMLStreamReader stream) { + this.stream = stream; + try { + if (stream.getEventType() != XMLStreamReader.START_DOCUMENT) { + throw new OMException("Expected START_DOCUMENT as first event from parser"); + } + if (stream.next() != XMLStreamReader.START_ELEMENT) { + throw new OMException("Expected START_ELEMENT event"); + } + } catch (XMLStreamException ex) { + throw new OMException(ex); + } + } + + @Override + public int read(char[] cbuf, int off, int len) throws IOException { + if (endOfStream) { + return -1; + } + int read = 0; + try { + while (true) { + if (sourceStart == -1) { + eventLoop: while (true) { + int type = stream.next(); + switch (type) { + case XMLStreamReader.CHARACTERS: + case XMLStreamReader.CDATA: + if (skipDepth == 0) { + sourceStart = 0; + break eventLoop; + } + break; + case XMLStreamReader.START_ELEMENT: + skipDepth++; + break; + case XMLStreamReader.END_ELEMENT: + if (skipDepth == 0) { + if (stream.next() == XMLStreamReader.END_DOCUMENT) { + endOfStream = true; + stream.close(); + return read == 0 ? -1 : read; + } else { + throw new IOException( + "End of document expected after element"); + } + } else { + skipDepth--; + } + } + } + } + int c = stream.getTextCharacters(sourceStart, cbuf, off, len); + sourceStart += c; + off += c; + len -= c; + read += c; + if (len > 0) { + sourceStart = -1; + } else { + return read; + } + } + } catch (XMLStreamException ex) { + IOException ex2 = new IOException("Got an exception from the underlying parser " + + "while reading the content of an element"); + ex2.initCause(ex); + throw ex2; + } + } + + @Override + public void close() throws IOException { + if (!endOfStream) { + try { + stream.close(); + } catch (XMLStreamException ex) { + IOException ex2 = new IOException("Error when trying to close underlying parser"); + ex2.initCause(ex); + throw ex2; + } + } + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextMessageBuilder.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextMessageBuilder.java new file mode 100644 index 0000000000..5c7623d7e2 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextMessageBuilder.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.format; + +import java.io.Reader; + +import org.apache.axiom.om.OMElement; +import org.apache.axis2.AxisFault; +import org.apache.axis2.builder.Builder; +import org.apache.axis2.context.MessageContext; + +/** + * Message builder able to build messages from a character stream. + * This interface can be optionally implemented by {@link Builder} + * implementations that support building a message from a character + * stream. + * <p> + * The character stream can either be provided as a string or a + * {@link Reader} object. The caller should use a {@link Reader} object + * except if the content of the message is available as a string anyway. + * <p> + * This interface is currently used by the JMS transport to process + * {@link javax.jms.TextMessage} instances. + */ +public interface TextMessageBuilder extends Builder { + public OMElement processDocument(Reader reader, String contentType, + MessageContext messageContext) throws AxisFault; + + public OMElement processDocument(String content, String contentType, + MessageContext messageContext) throws AxisFault; +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextMessageBuilderAdapter.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextMessageBuilderAdapter.java new file mode 100644 index 0000000000..692c4900b7 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextMessageBuilderAdapter.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.format; + +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; + +import javax.mail.internet.ContentType; +import javax.mail.internet.ParseException; + +import org.apache.axiom.om.OMElement; +import org.apache.axis2.AxisFault; +import org.apache.axis2.Constants; +import org.apache.axis2.builder.Builder; +import org.apache.axis2.context.MessageContext; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.streams.ReaderInputStream; + +/** + * Adapter to add the {@link TextMessageBuilder} interface to an + * existing {@link Builder}. + * It implements the {@link TextMessageBuilder#processDocument(Reader, String, MessageContext)} + * and {@link TextMessageBuilder#processDocument(String, String, MessageContext)} by converting + * the character stream to a byte stream using {@link ReaderInputStream}. + * + * TODO: specifying encoding + */ +public class TextMessageBuilderAdapter implements TextMessageBuilder { + private final Builder builder; + + public TextMessageBuilderAdapter(Builder builder) { + this.builder = builder; + } + + public OMElement processDocument(InputStream inputStream, String contentType, + MessageContext messageContext) throws AxisFault { + return builder.processDocument(inputStream, contentType, messageContext); + } + + public OMElement processDocument(Reader reader, String contentType, + MessageContext messageContext) throws AxisFault { + String charset; + try { + ContentType ct = new ContentType(contentType); + charset = ct.getParameter("charset"); + } catch (ParseException ex) { + charset = null; + } + if (charset == null) { + charset = MessageContext.DEFAULT_CHAR_SET_ENCODING; + } + messageContext.setProperty(Constants.Configuration.CHARACTER_SET_ENCODING, charset); + return processDocument(new ReaderInputStream(reader, charset), contentType, + messageContext); + } + + public OMElement processDocument(String content, String contentType, + MessageContext messageContext) throws AxisFault { + return processDocument(new StringReader(content), contentType, messageContext); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/WrappedTextNodeOMDataSourceFromDataSource.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/WrappedTextNodeOMDataSourceFromDataSource.java new file mode 100644 index 0000000000..d8a5e65f59 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/WrappedTextNodeOMDataSourceFromDataSource.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.format; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.nio.charset.Charset; + +import javax.activation.DataSource; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; + +import org.apache.axiom.om.OMDataSourceExt; +import org.apache.axiom.om.OMOutputFormat; +import org.apache.axiom.om.ds.OMDataSourceExtBase; +import org.apache.axiom.om.impl.MTOMXMLStreamWriter; +import org.apache.axiom.om.impl.serialize.StreamingOMSerializer; +import org.apache.axiom.om.util.StAXUtils; + +/** + * {@link org.apache.axiom.om.OMDataSource} implementation that represents a text node wrapped + * inside an element. The text data is provided by a {@link DataSource} object. + */ +public class WrappedTextNodeOMDataSourceFromDataSource extends OMDataSourceExtBase { + private final QName wrapperElementName; + private final DataSource binaryData; + private final Charset charset; + + public WrappedTextNodeOMDataSourceFromDataSource(QName wrapperElementName, DataSource binaryData, + Charset charset) { + this.wrapperElementName = wrapperElementName; + this.binaryData = binaryData; + this.charset = charset; + } + + @Override + public void serialize(OutputStream out, OMOutputFormat format) throws XMLStreamException { + XMLStreamWriter writer = new MTOMXMLStreamWriter(out, format); + serialize(writer); + writer.flush(); + } + + @Override + public void serialize(Writer writer, OMOutputFormat format) throws XMLStreamException { + MTOMXMLStreamWriter xmlWriter = + new MTOMXMLStreamWriter(StAXUtils.createXMLStreamWriter(writer)); + xmlWriter.setOutputFormat(format); + serialize(xmlWriter); + xmlWriter.flush(); + } + + @Override + public void serialize(XMLStreamWriter xmlWriter) throws XMLStreamException { + StreamingOMSerializer serializer = new StreamingOMSerializer(); + serializer.serialize(getReader(), xmlWriter); + } + + public XMLStreamReader getReader() throws XMLStreamException { + InputStream is; + try { + is = binaryData.getInputStream(); + } + catch (IOException ex) { + throw new XMLStreamException(ex); + } + return new WrappedTextNodeStreamReader(wrapperElementName, new InputStreamReader(is, charset)); + } + + public Object getObject() { + return binaryData; + } + + public boolean isDestructiveRead() { + return false; + } + + public boolean isDestructiveWrite() { + return false; + } + + public byte[] getXMLBytes(String encoding) throws UnsupportedEncodingException { + throw new UnsupportedOperationException(); + } + + public void close() { + } + + public OMDataSourceExt copy() { + return new WrappedTextNodeOMDataSourceFromDataSource(wrapperElementName, binaryData, charset); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/WrappedTextNodeOMDataSourceFromReader.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/WrappedTextNodeOMDataSourceFromReader.java new file mode 100644 index 0000000000..21c35f610c --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/WrappedTextNodeOMDataSourceFromReader.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.format; + +import java.io.IOException; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.io.Writer; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; + +import org.apache.axiom.om.OMDataSourceExt; +import org.apache.axiom.om.OMOutputFormat; +import org.apache.axiom.om.ds.OMDataSourceExtBase; +import org.apache.axiom.om.impl.MTOMXMLStreamWriter; +import org.apache.axiom.om.impl.serialize.StreamingOMSerializer; +import org.apache.axiom.om.util.StAXUtils; + +/** + * {@link org.apache.axiom.om.OMDataSource} implementation that represents a text node wrapped + * inside an element. The text data is provided by a {@link Reader} object. Since the stream + * can only be read once, this data source is destructive. + */ +public class WrappedTextNodeOMDataSourceFromReader extends OMDataSourceExtBase { + private final QName wrapperElementName; + private final Reader reader; + + public WrappedTextNodeOMDataSourceFromReader(QName wrapperElementName, Reader reader) { + this.wrapperElementName = wrapperElementName; + this.reader = reader; + } + + @Override + public void serialize(Writer writer, OMOutputFormat format) throws XMLStreamException { + MTOMXMLStreamWriter xmlWriter = + new MTOMXMLStreamWriter(StAXUtils.createXMLStreamWriter(writer)); + xmlWriter.setOutputFormat(format); + serialize(xmlWriter); + xmlWriter.flush(); + } + + @Override + public void serialize(XMLStreamWriter xmlWriter) throws XMLStreamException { + StreamingOMSerializer serializer = new StreamingOMSerializer(); + serializer.serialize(getReader(), xmlWriter); + } + + public XMLStreamReader getReader() throws XMLStreamException { + return new WrappedTextNodeStreamReader(wrapperElementName, reader); + } + + public Object getObject() { + return null; + } + + public boolean isDestructiveRead() { + return true; + } + + public boolean isDestructiveWrite() { + return true; + } + + public byte[] getXMLBytes(String encoding) throws UnsupportedEncodingException { + throw new UnsupportedOperationException(); + } + + public void close() { + try { + reader.close(); + } catch (IOException ex) { + // Ignore + } + } + + public OMDataSourceExt copy() { + throw new UnsupportedOperationException(); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/WrappedTextNodeStreamReader.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/WrappedTextNodeStreamReader.java new file mode 100644 index 0000000000..c656949b1f --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/WrappedTextNodeStreamReader.java @@ -0,0 +1,437 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.format; + +import java.io.IOException; +import java.io.Reader; +import java.util.Collections; + +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; +import javax.xml.namespace.QName; +import javax.xml.stream.Location; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import org.apache.axiom.om.impl.EmptyOMLocation; +import org.apache.axiom.om.impl.llom.util.NamespaceContextImpl; +import org.apache.commons.io.IOUtils; + +/** + * {@link XMLInputStreamReader} implementation that + * represents a text node wrapped inside an element. The text data is provided by a + * {@link java.io.Reader Reader}. + * <p> + * It will produce the following sequence of XML events: + * <ul> + * <li>START_DOCUMENT</li> + * <li>START_ELEMENT</li> + * <li>(CHARACTER)*</li> + * <li>END_ELEMENT</li> + * <li>END_DOCMENT</li> + * </ul> + * The class is implemented as a simple state machine, where the state is identified + * by the current event type. The initial state is <tt>START_DOCUMENT</tt> and the + * following transitions are triggered by {@link #next()}: + * <ul> + * <li>START_DOCUMENT → START_ELEMENT</li> + * <li>START_ELEMENT → END_ELEMENT (if character stream is empty)</li> + * <li>START_ELEMENT → CHARACTERS (if character stream is not empty)</li> + * <li>CHARACTERS → CHARACTERS (if data available in stream)</li> + * <li>CHARACTERS → END_ELEMENT (if end of stream reached)</li> + * <li>END_ELEMENT → END_DOCUMENT</li> + * </ul> + * Additionally, {@link #getElementText()} triggers the following transition: + * <ul> + * <li>START_ELEMENT → END_ELEMENT</li> + * </ul> + * Note that since multiple consecutive CHARACTERS events may be returned, this + * "parser" is not coalescing. + * + */ +// TODO: This class has been copied from Synapse (package org.apache.synapse.util). +// Once it has been moved to Axis2 or Axiom, remove the duplicate from Synapse. +public class WrappedTextNodeStreamReader implements XMLStreamReader { + /** + * Location object returned by {@link #getLocation()}. + * It always returns -1 for the location and null for the publicId and systemId. + */ + private final static Location EMPTY_LOCATION = new EmptyOMLocation(); + + /** + * The qualified name of the wrapper element. + */ + private final QName wrapperElementName; + + /** + * The Reader object that represents the text data. + */ + private final Reader reader; + + /** + * The maximum number of characters to return for each CHARACTER event. + */ + private final int chunkSize; + + /** + * The type of the current XML event. + */ + private int eventType = START_DOCUMENT; + + /** + * The character data for the current event. This is only set if the current + * event is a CHARACTER event. The size of the array is determined by + * {@link #chunkSize} + */ + private char[] charData; + + /** + * The length of the character data in {@link #charData}. + */ + private int charDataLength; + + /** + * The namespace context applicable in the scope of the wrapper element. + * Beside the default mappings for xml and xmlns, it only contains the + * mapping for the namespace of the wrapper element. + * This attribute is initialized lazily by {@link #getNamespaceContext()}. + */ + private NamespaceContext namespaceContext; + + /** + * Create a new instance. + * + * @param wrapperElementName the qualified name of the wrapper element + * @param reader the Reader object holding the character data to be wrapped + * @param chunkSize the maximum number of characters that are returned for each CHARACTER event + */ + public WrappedTextNodeStreamReader(QName wrapperElementName, Reader reader, int chunkSize) { + this.wrapperElementName = wrapperElementName; + this.reader = reader; + this.chunkSize = chunkSize; + } + + /** + * Create a new instance with chunk size 4096. + * + * @param wrapperElementName the qualified name of the wrapper element + * @param reader the Reader object holding the character data to be wrapped + */ + public WrappedTextNodeStreamReader(QName wrapperElementName, Reader reader) { + this(wrapperElementName, reader, 4096); + } + + public Object getProperty(String name) throws IllegalArgumentException { + // We don't define any properties + return null; + } + + // + // Methods to manipulate the parser state + // + + public boolean hasNext() throws XMLStreamException { + return eventType != END_DOCUMENT; + } + + public int next() throws XMLStreamException { + // Determine next event type based on current event type. If current event type + // is START_ELEMENT or CHARACTERS, pull new data from the reader. + switch (eventType) { + case START_DOCUMENT: + eventType = START_ELEMENT; + break; + case START_ELEMENT: + charData = new char[chunkSize]; + // No break here! + case CHARACTERS: + try { + charDataLength = reader.read(charData); + } + catch (IOException ex) { + throw new XMLStreamException(ex); + } + if (charDataLength == -1) { + charData = null; + eventType = END_ELEMENT; + } else { + eventType = CHARACTERS; + } + break; + case END_ELEMENT: + eventType = END_DOCUMENT; + break; + default: + throw new IllegalStateException(); + } + return eventType; + } + + public int nextTag() throws XMLStreamException { + // We don't have white space, comments or processing instructions + throw new XMLStreamException("Current event is not white space"); + } + + public int getEventType() { + return eventType; + } + + public boolean isStartElement() { return eventType == START_ELEMENT; } + public boolean isEndElement() { return eventType == END_ELEMENT; } + public boolean isCharacters() { return eventType == CHARACTERS; } + public boolean isWhiteSpace() { return false; } + public boolean hasText() { return eventType == CHARACTERS; } + public boolean hasName() { return eventType == START_ELEMENT || eventType == END_ELEMENT; } + + public void require(int type, String namespaceURI, String localName) throws XMLStreamException { + if (type != eventType + || (namespaceURI != null && !namespaceURI.equals(getNamespaceURI())) + || (localName != null && !namespaceURI.equals(getLocalName()))) { + throw new XMLStreamException("Unexpected event type"); + } + } + + public Location getLocation() { + // We do not support location information + return EMPTY_LOCATION; + } + + public void close() throws XMLStreamException { + // Javadoc says that this method should not close the underlying input source, + // but we need to close the reader somewhere. + try { + reader.close(); + } + catch (IOException ex) { + throw new XMLStreamException(ex); + } + } + + // + // Methods related to the xml declaration. + // + + public String getEncoding() { + // Encoding is not known (not relevant?) + return null; + } + + public String getCharacterEncodingScheme() { + // Encoding is not known (not relevant?) + return null; + } + + public String getVersion() { + // Version is not relevant + return null; + } + + public boolean standaloneSet() { + return false; + } + + public boolean isStandalone() { + return true; + } + + // + // Methods related to the namespace context + // + + public NamespaceContext getNamespaceContext() { + if (namespaceContext == null) { + namespaceContext = new NamespaceContextImpl(Collections.singletonMap(wrapperElementName.getPrefix(), wrapperElementName.getNamespaceURI())); + } + return namespaceContext; + } + + public String getNamespaceURI(String prefix) { + String namespaceURI = getNamespaceContext().getNamespaceURI(prefix); + // NamespaceContext#getNamespaceURI and XMLStreamReader#getNamespaceURI have slightly + // different semantics for unbound prefixes. + return namespaceURI.equals(XMLConstants.NULL_NS_URI) ? null : prefix; + } + + // + // Methods related to elements + // + + private void checkStartElement() { + if (eventType != START_ELEMENT) { + throw new IllegalStateException(); + } + } + + public String getAttributeValue(String namespaceURI, String localName) { + checkStartElement(); + return null; + } + + public int getAttributeCount() { + checkStartElement(); + return 0; + } + + public QName getAttributeName(int index) { + checkStartElement(); + throw new ArrayIndexOutOfBoundsException(); + } + + public String getAttributeLocalName(int index) { + checkStartElement(); + throw new ArrayIndexOutOfBoundsException(); + } + + public String getAttributePrefix(int index) { + checkStartElement(); + throw new ArrayIndexOutOfBoundsException(); + } + + public String getAttributeNamespace(int index) { + checkStartElement(); + throw new ArrayIndexOutOfBoundsException(); + } + + public String getAttributeType(int index) { + checkStartElement(); + throw new ArrayIndexOutOfBoundsException(); + } + + public String getAttributeValue(int index) { + checkStartElement(); + throw new ArrayIndexOutOfBoundsException(); + } + + public boolean isAttributeSpecified(int index) { + checkStartElement(); + throw new ArrayIndexOutOfBoundsException(); + } + + private void checkElement() { + if (eventType != START_ELEMENT && eventType != END_ELEMENT) { + throw new IllegalStateException(); + } + } + + public QName getName() { + return null; + } + + public String getLocalName() { + checkElement(); + return wrapperElementName.getLocalPart(); + } + + public String getPrefix() { + return wrapperElementName.getPrefix(); + } + + public String getNamespaceURI() { + checkElement(); + return wrapperElementName.getNamespaceURI(); + } + + public int getNamespaceCount() { + checkElement(); + // There is one namespace declared on the wrapper element + return 1; + } + + public String getNamespacePrefix(int index) { + checkElement(); + if (index == 0) { + return wrapperElementName.getPrefix(); + } else { + throw new IndexOutOfBoundsException(); + } + } + + public String getNamespaceURI(int index) { + checkElement(); + if (index == 0) { + return wrapperElementName.getNamespaceURI(); + } else { + throw new IndexOutOfBoundsException(); + } + } + + public String getElementText() throws XMLStreamException { + if (eventType == START_ELEMENT) { + // Actually the purpose of this class is to avoid storing + // the character data entirely in memory, but if the caller + // wants a String, we don't have the choice... + try { + String result = IOUtils.toString(reader); + eventType = END_ELEMENT; + return result; + } + catch (IOException ex) { + throw new XMLStreamException(ex); + } + } else { + throw new XMLStreamException("Current event is not a START_ELEMENT"); + } + } + + private void checkCharacters() { + if (eventType != CHARACTERS) { + throw new IllegalStateException(); + } + } + + public String getText() { + checkCharacters(); + return new String(charData, 0, charDataLength); + } + + public char[] getTextCharacters() { + checkCharacters(); + return charData; + } + + public int getTextStart() { + checkCharacters(); + return 0; + } + + public int getTextLength() { + checkCharacters(); + return charDataLength; + } + + public int getTextCharacters(int sourceStart, char[] target, int targetStart, int length) throws XMLStreamException { + checkCharacters(); + int c = Math.min(charDataLength-sourceStart, length); + System.arraycopy(charData, sourceStart, target, targetStart, c); + return c; + } + + // + // Methods related to processing instructions + // + + public String getPIData() { + throw new IllegalStateException(); + } + + public String getPITarget() { + throw new IllegalStateException(); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/AxisJMSException.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/AxisJMSException.java new file mode 100644 index 0000000000..ec53a2a1ca --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/AxisJMSException.java @@ -0,0 +1,31 @@ +/* +* Copyright 2004,2005 The Apache Software Foundation. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.jms; + +public class AxisJMSException extends RuntimeException { + + AxisJMSException() { + super(); + } + + AxisJMSException(String msg) { + super(msg); + } + + AxisJMSException(String msg, Exception e) { + super(msg, e); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/BytesMessageDataSource.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/BytesMessageDataSource.java new file mode 100644 index 0000000000..5228efa154 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/BytesMessageDataSource.java @@ -0,0 +1,72 @@ +/* +* Copyright 2004,2005 The Apache Software Foundation. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.jms; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import javax.jms.BytesMessage; +import javax.jms.JMSException; + +/** + * Data source implementation wrapping a JMS {@link BytesMessage}. + * <p> + * Note that two input streams created by the same instance of this + * class can not be used at the same time. + */ +public class BytesMessageDataSource implements SizeAwareDataSource { + private final BytesMessage message; + private final String contentType; + + public BytesMessageDataSource(BytesMessage message, String contentType) { + this.message = message; + this.contentType = contentType; + } + + public BytesMessageDataSource(BytesMessage message) { + this(message, "application/octet-stream"); + } + + public long getSize() { + try { + return message.getBodyLength(); + } catch (JMSException ex) { + throw new RuntimeException(ex); + } + } + + public String getContentType() { + return contentType; + } + + public InputStream getInputStream() throws IOException { + try { + message.reset(); + } catch (JMSException ex) { + throw new JMSExceptionWrapper(ex); + } + return new BytesMessageInputStream(message); + } + + public String getName() { + return null; + } + + public OutputStream getOutputStream() throws IOException { + throw new UnsupportedOperationException(); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/BytesMessageInputStream.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/BytesMessageInputStream.java new file mode 100644 index 0000000000..9080641572 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/BytesMessageInputStream.java @@ -0,0 +1,75 @@ +/* +* Copyright 2004,2005 The Apache Software Foundation. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.jms; + +import java.io.InputStream; + +import javax.jms.BytesMessage; +import javax.jms.JMSException; +import javax.jms.MessageEOFException; + +/** + * Input stream that reads data from a JMS {@link BytesMessage}. + * Note that since the current position in the message is managed by + * the underlying {@link BytesMessage} object, it is not possible to + * use several instances of this class operating on a single + * {@link BytesMessage} at the same time. + */ +public class BytesMessageInputStream extends InputStream { + private final BytesMessage message; + + public BytesMessageInputStream(BytesMessage message) { + this.message = message; + } + + @Override + public int read() throws JMSExceptionWrapper { + try { + return message.readByte() & 0xFF; + } catch (MessageEOFException ex) { + return -1; + } catch (JMSException ex) { + throw new JMSExceptionWrapper(ex); + } + } + + @Override + public int read(byte[] b, int off, int len) throws JMSExceptionWrapper { + if (off == 0) { + try { + return message.readBytes(b, len); + } catch (JMSException ex) { + throw new JMSExceptionWrapper(ex); + } + } else { + byte[] b2 = new byte[len]; + int c = read(b2); + if (c > 0) { + System.arraycopy(b2, 0, b, off, c); + } + return c; + } + } + + @Override + public int read(byte[] b) throws JMSExceptionWrapper { + try { + return message.readBytes(b); + } catch (JMSException ex) { + throw new JMSExceptionWrapper(ex); + } + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/BytesMessageOutputStream.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/BytesMessageOutputStream.java new file mode 100644 index 0000000000..4508d68280 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/BytesMessageOutputStream.java @@ -0,0 +1,56 @@ +/* +* Copyright 2004,2005 The Apache Software Foundation. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.jms; + +import java.io.OutputStream; + +import javax.jms.BytesMessage; +import javax.jms.JMSException; + +public class BytesMessageOutputStream extends OutputStream { + private final BytesMessage message; + + public BytesMessageOutputStream(BytesMessage message) { + this.message = message; + } + + @Override + public void write(int b) throws JMSExceptionWrapper { + try { + message.writeByte((byte)b); + } catch (JMSException ex) { + throw new JMSExceptionWrapper(ex); + } + } + + @Override + public void write(byte[] b, int off, int len) throws JMSExceptionWrapper { + try { + message.writeBytes(b, off, len); + } catch (JMSException ex) { + new JMSExceptionWrapper(ex); + } + } + + @Override + public void write(byte[] b) throws JMSExceptionWrapper { + try { + message.writeBytes(b); + } catch (JMSException ex) { + throw new JMSExceptionWrapper(ex); + } + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSConnectionFactory.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSConnectionFactory.java new file mode 100644 index 0000000000..d5d164ce76 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSConnectionFactory.java @@ -0,0 +1,393 @@ +/* +* Copyright 2004,2005 The Apache Software Foundation. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.jms; + +import java.util.Hashtable; + +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.MessageProducer; +import javax.jms.Session; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; + +import org.apache.axiom.om.OMElement; +import org.apache.axis2.AxisFault; +import org.apache.axis2.description.Parameter; +import org.apache.axis2.description.ParameterIncludeImpl; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Encapsulate a JMS Connection factory definition within an Axis2.xml + * + * JMS Connection Factory definitions, allows JNDI properties as well as other service + * level parameters to be defined, and re-used by each service that binds to it + * + * When used for sending messages out, the JMSConnectionFactory'ies are able to cache + * a Connection, Session or Producer + */ +public class JMSConnectionFactory { + + private static final Log log = LogFactory.getLog(JMSConnectionFactory.class); + + /** The name used for the connection factory definition within Axis2 */ + private String name = null; + /** The list of parameters from the axis2.xml definition */ + private Hashtable<String, String> parameters = new Hashtable<String, String>(); + + /** The cached InitialContext reference */ + private Context context = null; + /** The JMS ConnectionFactory this definition refers to */ + private ConnectionFactory conFactory = null; + /** The shared JMS Connection for this JMS connection factory */ + private Connection sharedConnection = null; + /** The shared JMS Session for this JMS connection factory */ + private Session sharedSession = null; + /** The shared JMS MessageProducer for this JMS connection factory */ + private MessageProducer sharedProducer = null; + /** The Shared Destination */ + private Destination sharedDestination = null; + /** The shared JMS connection for this JMS connection factory */ + private int cacheLevel = JMSConstants.CACHE_CONNECTION; + + /** + * Digest a JMS CF definition from an axis2.xml 'Parameter' and construct + * @param parameter the axis2.xml 'Parameter' that defined the JMS CF + */ + public JMSConnectionFactory(Parameter parameter) { + + this.name = parameter.getName(); + ParameterIncludeImpl pi = new ParameterIncludeImpl(); + + try { + pi.deserializeParameters((OMElement) parameter.getValue()); + } catch (AxisFault axisFault) { + handleException("Error reading parameters for JMS connection factory" + name, axisFault); + } + + for (Object o : pi.getParameters()) { + Parameter p = (Parameter) o; + parameters.put(p.getName(), (String) p.getValue()); + } + + digestCacheLevel(); + try { + context = new InitialContext(parameters); + conFactory = JMSUtils.lookup(context, ConnectionFactory.class, + parameters.get(JMSConstants.PARAM_CONFAC_JNDI_NAME)); + if (parameters.get(JMSConstants.PARAM_DESTINATION) != null) { + sharedDestination = JMSUtils.lookup(context, Destination.class, + parameters.get(JMSConstants.PARAM_DESTINATION)); + } + log.info("JMS ConnectionFactory : " + name + " initialized"); + + } catch (NamingException e) { + throw new AxisJMSException("Cannot acquire JNDI context, JMS Connection factory : " + + parameters.get(JMSConstants.PARAM_CONFAC_JNDI_NAME) + " or default destination : " + + parameters.get(JMSConstants.PARAM_DESTINATION) + + " for JMS CF : " + name + " using : " + parameters); + } + } + + /** + * Digest, the cache value iff specified + */ + private void digestCacheLevel() { + + String key = JMSConstants.PARAM_CACHE_LEVEL; + String val = parameters.get(key); + + if ("none".equalsIgnoreCase(val)) { + this.cacheLevel = JMSConstants.CACHE_NONE; + } else if ("connection".equalsIgnoreCase(val)) { + this.cacheLevel = JMSConstants.CACHE_CONNECTION; + } else if ("session".equals(val)){ + this.cacheLevel = JMSConstants.CACHE_SESSION; + } else if ("producer".equals(val)) { + this.cacheLevel = JMSConstants.CACHE_PRODUCER; + } else if (val != null) { + throw new AxisJMSException("Invalid cache level : " + val + " for JMS CF : " + name); + } + } + + /** + * Return the name assigned to this JMS CF definition + * @return name of the JMS CF + */ + public String getName() { + return name; + } + + /** + * The list of properties (including JNDI and non-JNDI) + * @return properties defined on the JMS CF + */ + public Hashtable<String, String> getParameters() { + return parameters; + } + + /** + * Get cached InitialContext + * @return cache InitialContext + */ + public Context getContext() { + return context; + } + + /** + * Cache level applicable for this JMS CF + * @return applicable cache level + */ + public int getCacheLevel() { + return cacheLevel; + } + + /** + * Get the shared Destination - if defined + * @return + */ + public Destination getSharedDestination() { + return sharedDestination; + } + + /** + * Lookup a Destination using this JMS CF definitions and JNDI name + * @param name JNDI name of the Destionation + * @return JMS Destination for the given JNDI name or null + */ + public Destination getDestination(String name) { + try { + return JMSUtils.lookup(context, Destination.class, name); + } catch (NamingException e) { + handleException("Unknown JMS Destination : " + name + " using : " + parameters, e); + } + return null; + } + + /** + * Get the reply Destination from the PARAM_REPLY_DESTINATION parameter + * @return reply destination defined in the JMS CF + */ + public String getReplyToDestination() { + return parameters.get(JMSConstants.PARAM_REPLY_DESTINATION); + } + + private void handleException(String msg, Exception e) { + log.error(msg, e); + throw new AxisJMSException(msg, e); + } + + /** + * Should the JMS 1.1 API be used? - defaults to yes + * @return true, if JMS 1.1 api should be used + */ + public boolean isJmsSpec11() { + return parameters.get(JMSConstants.PARAM_JMS_SPEC_VER) == null || + "1.1".equals(parameters.get(JMSConstants.PARAM_JMS_SPEC_VER)); + } + + /** + * Return the type of the JMS CF Destination + * @return TRUE if a Queue, FALSE for a Topic and NULL for a JMS 1.1 Generic Destination + */ + public Boolean isQueue() { + if (parameters.get(JMSConstants.PARAM_CONFAC_TYPE) == null && + parameters.get(JMSConstants.PARAM_DEST_TYPE) == null) { + return null; + } + + if (parameters.get(JMSConstants.PARAM_CONFAC_TYPE) != null) { + if ("queue".equalsIgnoreCase(parameters.get(JMSConstants.PARAM_CONFAC_TYPE))) { + return true; + } else if ("topic".equalsIgnoreCase(parameters.get(JMSConstants.PARAM_CONFAC_TYPE))) { + return false; + } else { + throw new AxisJMSException("Invalid " + JMSConstants.PARAM_CONFAC_TYPE + " : " + + parameters.get(JMSConstants.PARAM_CONFAC_TYPE) + " for JMS CF : " + name); + } + } else { + if ("queue".equalsIgnoreCase(parameters.get(JMSConstants.PARAM_DEST_TYPE))) { + return true; + } else if ("topic".equalsIgnoreCase(parameters.get(JMSConstants.PARAM_DEST_TYPE))) { + return false; + } else { + throw new AxisJMSException("Invalid " + JMSConstants.PARAM_DEST_TYPE + " : " + + parameters.get(JMSConstants.PARAM_DEST_TYPE) + " for JMS CF : " + name); + } + } + } + + /** + * Is a session transaction requested from users of this JMS CF? + * @return session transaction required by the clients of this? + */ + private boolean isSessionTransacted() { + return parameters.get(JMSConstants.PARAM_SESSION_TRANSACTED) == null || + Boolean.valueOf(parameters.get(JMSConstants.PARAM_SESSION_TRANSACTED)); + } + + /** + * Create a new Connection + * @return a new Connection + */ + private Connection createConnection() { + + Connection connection = null; + try { + connection = JMSUtils.createConnection( + conFactory, + parameters.get(JMSConstants.PARAM_JMS_USERNAME), + parameters.get(JMSConstants.PARAM_JMS_PASSWORD), + isJmsSpec11(), isQueue()); + + if (log.isDebugEnabled()) { + log.debug("New JMS Connection from JMS CF : " + name + " created"); + } + + } catch (JMSException e) { + handleException("Error acquiring a Connection from the JMS CF : " + name + + " using properties : " + parameters, e); + } + return connection; + } + + /** + * Create a new Session + * @param connection Connection to use + * @return A new Session + */ + private Session createSession(Connection connection) { + try { + if (log.isDebugEnabled()) { + log.debug("Creating a new JMS Session from JMS CF : " + name); + } + return JMSUtils.createSession( + connection, isSessionTransacted(), Session.AUTO_ACKNOWLEDGE, isJmsSpec11(), isQueue()); + + } catch (JMSException e) { + handleException("Error creating JMS session from JMS CF : " + name, e); + } + return null; + } + + /** + * Create a new MessageProducer + * @param session Session to be used + * @param destination Destination to be used + * @return a new MessageProducer + */ + private MessageProducer createProducer(Session session, Destination destination) { + try { + if (log.isDebugEnabled()) { + log.debug("Creating a new JMS MessageProducer from JMS CF : " + name); + } + + return JMSUtils.createProducer( + session, destination, isQueue(), isJmsSpec11()); + + } catch (JMSException e) { + handleException("Error creating JMS producer from JMS CF : " + name,e); + } + return null; + } + + /** + * Get a new Connection or shared Connection from this JMS CF + * @return new or shared Connection from this JMS CF + */ + public Connection getConnection() { + if (cacheLevel > JMSConstants.CACHE_NONE) { + return getSharedConnection(); + } else { + return createConnection(); + } + } + + /** + * Get a new Session or shared Session from this JMS CF + * @param connection the Connection to be used + * @return new or shared Session from this JMS CF + */ + public Session getSession(Connection connection) { + if (cacheLevel > JMSConstants.CACHE_CONNECTION) { + return getSharedSession(); + } else { + return createSession((connection == null ? getConnection() : connection)); + } + } + + /** + * Get a new MessageProducer or shared MessageProducer from this JMS CF + * @param connection the Connection to be used + * @param session the Session to be used + * @param destination the Destination to bind MessageProducer to + * @return new or shared MessageProducer from this JMS CF + */ + public MessageProducer getMessageProducer( + Connection connection, Session session, Destination destination) { + if (cacheLevel > JMSConstants.CACHE_SESSION) { + return getSharedProducer(); + } else { + return createProducer((session == null ? getSession(connection) : session), destination); + } + } + + /** + * Get a new Connection or shared Connection from this JMS CF + * @return new or shared Connection from this JMS CF + */ + private Connection getSharedConnection() { + if (sharedConnection == null) { + sharedConnection = createConnection(); + if (log.isDebugEnabled()) { + log.debug("Created shared JMS Connection for JMS CF : " + name); + } + } + return sharedConnection; + } + + /** + * Get a shared Session from this JMS CF + * @return shared Session from this JMS CF + */ + private Session getSharedSession() { + if (sharedSession == null) { + sharedSession = createSession(getSharedConnection()); + if (log.isDebugEnabled()) { + log.debug("Created shared JMS Session for JMS CF : " + name); + } + } + return sharedSession; + } + + /** + * Get a shared MessageProducer from this JMS CF + * @return shared MessageProducer from this JMS CF + */ + private MessageProducer getSharedProducer() { + if (sharedProducer == null) { + sharedProducer = createProducer(getSharedSession(), sharedDestination); + if (log.isDebugEnabled()) { + log.debug("Created shared JMS MessageConsumer for JMS CF : " + name); + } + } + return sharedProducer; + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSConnectionFactoryManager.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSConnectionFactoryManager.java new file mode 100644 index 0000000000..fb16500efc --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSConnectionFactoryManager.java @@ -0,0 +1,122 @@ +/* +* Copyright 2004,2005 The Apache Software Foundation. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.jms; + +import java.util.HashMap; +import java.util.Map; + +import javax.naming.Context; + +import org.apache.axis2.AxisFault; +import org.apache.axis2.description.Parameter; +import org.apache.axis2.description.ParameterInclude; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Class managing a set of {@link JMSConnectionFactory} objects. + */ +public class JMSConnectionFactoryManager { + + private static final Log log = LogFactory.getLog(JMSConnectionFactoryManager.class); + + /** A Map containing the JMS connection factories managed by this, keyed by name */ + private final Map<String,JMSConnectionFactory> connectionFactories = + new HashMap<String,JMSConnectionFactory>(); + + /** + * Construct a Connection factory manager for the JMS transport sender or receiver + * @param trpInDesc + */ + public JMSConnectionFactoryManager(ParameterInclude trpInDesc) { + loadConnectionFactoryDefinitions(trpInDesc); + } + + /** + * Create JMSConnectionFactory instances for the definitions in the transport configuration, + * and add these into our collection of connectionFactories map keyed by name + * + * @param trpDesc the transport description for JMS + */ + private void loadConnectionFactoryDefinitions(ParameterInclude trpDesc) { + + for (Object o : trpDesc.getParameters()) { + Parameter p = (Parameter)o; + try { + JMSConnectionFactory jmsConFactory = new JMSConnectionFactory(p); + connectionFactories.put(jmsConFactory.getName(), jmsConFactory); + } catch (AxisJMSException e) { + log.error("Error setting up connection factory : " + p.getName(), e); + } + } + } + + /** + * Get the JMS connection factory with the given name. + * + * @param name the name of the JMS connection factory + * @return the JMS connection factory or null if no connection factory with + * the given name exists + */ + public JMSConnectionFactory getJMSConnectionFactory(String name) { + return connectionFactories.get(name); + } + + /** + * Get the JMS connection factory that matches the given properties, i.e. referring to + * the same underlying connection factory. Used by the JMSSender to determine if already + * available resources should be used for outgoing messages + * + * @param props a Map of connection factory JNDI properties and name + * @return the JMS connection factory or null if no connection factory compatible + * with the given properties exists + */ + public JMSConnectionFactory getJMSConnectionFactory(Map<String,String> props) { + for (JMSConnectionFactory cf : connectionFactories.values()) { + Map<String,String> cfProperties = cf.getParameters(); + + if (equals(props.get(JMSConstants.PARAM_CONFAC_JNDI_NAME), + cfProperties.get(JMSConstants.PARAM_CONFAC_JNDI_NAME)) + && + equals(props.get(Context.INITIAL_CONTEXT_FACTORY), + cfProperties.get(Context.INITIAL_CONTEXT_FACTORY)) + && + equals(props.get(Context.PROVIDER_URL), + cfProperties.get(Context.PROVIDER_URL)) + && + equals(props.get(Context.SECURITY_PRINCIPAL), + cfProperties.get(Context.SECURITY_PRINCIPAL)) + && + equals(props.get(Context.SECURITY_CREDENTIALS), + cfProperties.get(Context.SECURITY_CREDENTIALS))) { + return cf; + } + } + return null; + } + + /** + * Compare two values preventing NPEs + */ + private static boolean equals(Object s1, Object s2) { + return s1 == s2 || s1 != null && s1.equals(s2); + } + + protected void handleException(String msg, Exception e) throws AxisFault { + log.error(msg, e); + throw new AxisFault(msg, e); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSConstants.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSConstants.java new file mode 100644 index 0000000000..6a11201625 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSConstants.java @@ -0,0 +1,273 @@ +/* +* Copyright 2004,2005 The Apache Software Foundation. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.jms; + +import org.apache.axis2.client.Options; + +public class JMSConstants { + + /** + * The prefix indicating an Axis JMS URL + */ + public static final String JMS_PREFIX = "jms:/"; + + //------------------------------------ defaults / constants ------------------------------------ + /** + * The local (Axis2) JMS connection factory name of the default connection + * factory to be used, if a service does not explicitly state the connection + * factory it should be using by a Parameter named JMSConstants.CONFAC_PARAM + */ + public static final String DEFAULT_CONFAC_NAME = "default"; + /** + * The default JMS time out waiting for a reply - also see {@link JMS_WAIT_REPLY} + */ + public static final long DEFAULT_JMS_TIMEOUT = Options.DEFAULT_TIMEOUT_MILLISECONDS; + /** + * Value indicating a Queue used for {@link DEST_PARAM_TYPE}, {@link REPLY_PARAM_TYPE} + */ + public static final String DESTINATION_TYPE_QUEUE = "queue"; + /** + * Value indicating a Topic used for {@link DEST_PARAM_TYPE}, {@link REPLY_PARAM_TYPE} + */ + public static final String DESTINATION_TYPE_TOPIC = "topic"; + /** + * Value indicating a JMS 1.1 Generic Destination used by {@link DEST_PARAM_TYPE}, {@link REPLY_PARAM_TYPE} + */ + public static final String DESTINATION_TYPE_GENERIC = "generic"; + + /** Do not cache any JMS resources between tasks (when sending) or JMS CF's (when sending) */ + public static final int CACHE_NONE = 0; + /** Cache only the JMS connection between tasks (when receiving), or JMS CF's (when sending)*/ + public static final int CACHE_CONNECTION = 1; + /** Cache only the JMS connection and Session between tasks (receiving), or JMS CF's (sending) */ + public static final int CACHE_SESSION = 2; + /** Cache the JMS connection, Session and Consumer between tasks when receiving*/ + public static final int CACHE_CONSUMER = 3; + /** Cache the JMS connection, Session and Producer within a JMSConnectionFactory when sending */ + public static final int CACHE_PRODUCER = 4; + /** automatic choice of an appropriate caching level (depending on the transaction strategy) */ + public static final int CACHE_AUTO = 5; + + /** A JMS 1.1 Generic Destination type or ConnectionFactory */ + public static final int GENERIC = 0; + /** A Queue Destination type or ConnectionFactory */ + public static final int QUEUE = 1; + /** A Topic Destination type or ConnectionFactory */ + public static final int TOPIC = 2; + + /** + * The EPR parameter name indicating the name of the message level property that indicated the content type. + */ + public static final String CONTENT_TYPE_PROPERTY_PARAM = "transport.jms.ContentTypeProperty"; + + //---------------------------------- services.xml parameters ----------------------------------- + /** + * The Service level Parameter name indicating the JMS destination for requests of a service + */ + public static final String PARAM_DESTINATION = "transport.jms.Destination"; + /** + * The Service level Parameter name indicating the destination type for requests. + * also see {@link DESTINATION_TYPE_QUEUE}, {@link DESTINATION_TYPE_TOPIC} + */ + public static final String PARAM_DEST_TYPE = "transport.jms.DestinationType"; + /** + * The Service level Parameter name indicating the [default] response destination of a service + */ + public static final String PARAM_REPLY_DESTINATION = "transport.jms.ReplyDestination"; + /** + * The Service level Parameter name indicating the response destination type + * also see {@link DESTINATION_TYPE_QUEUE}, {@link DESTINATION_TYPE_TOPIC} + */ + public static final String PARAM_REPLY_DEST_TYPE = "transport.jms.ReplyDestinationType"; + /** + * The Parameter name of an Axis2 service, indicating the JMS connection + * factory which should be used to listen for messages for it. This is + * the local (Axis2) name of the connection factory and not the JNDI name + */ + public static final String PARAM_JMS_CONFAC = "transport.jms.ConnectionFactory"; + /** + * Connection factory type if using JMS 1.0, either DESTINATION_TYPE_QUEUE or DESTINATION_TYPE_TOPIC + */ + public static final String PARAM_CONFAC_TYPE = "transport.jms.ConnectionFactoryType"; + /** + * The Parameter name indicating the JMS connection factory JNDI name + */ + public static final String PARAM_CONFAC_JNDI_NAME = "transport.jms.ConnectionFactoryJNDIName"; + /** + * The Parameter indicating the expected content type for messages received by the service. + */ + public static final String CONTENT_TYPE_PARAM = "transport.jms.ContentType"; + /** + * The Parameter indicating a final EPR as a String, to be published on the WSDL of a service + * Could occur more than once, and could provide additional connection properties or a subset + * of the properties auto computed. Also could replace IP addresses with hostnames, and expose + * public credentials clients. If a user specified this parameter, the auto generated EPR will + * not be exposed - unless an instance of this parameter is added with the string "legacy" + * This parameter could be used to expose EPR's conforming to the proposed SOAP/JMS spec + * until such time full support is implemented for it. + */ + public static final String PARAM_PUBLISH_EPR = "transport.jms.PublishEPR"; + /** The parameter indicating the JMS API specification to be used - if this is "1.1" the JMS + * 1.1 API would be used, else the JMS 1.0.2B + */ + public static final String PARAM_JMS_SPEC_VER = "transport.jms.JMSSpecVersion"; + + /** + * The Parameter indicating whether the JMS Session should be transacted for the service + * Specified as a "true" or "false" + */ + public static final String PARAM_SESSION_TRANSACTED = "transport.jms.SessionTransacted"; + /** + * The Parameter indicating the Session acknowledgement for the service. Must be one of the + * following Strings, or the appropriate Integer used by the JMS API + * "AUTO_ACKNOWLEDGE", "CLIENT_ACKNOWLEDGE", "DUPS_OK_ACKNOWLEDGE" or "SESSION_TRANSACTED" + */ + public static final String PARAM_SESSION_ACK = "transport.jms.SessionAcknowledgement"; + /** A message selector to be used when messages are sought for this service */ + public static final String PARAM_MSG_SELECTOR = "transport.jms.MessageSelector"; + /** Is the Subscription durable ? - "true" or "false" See {@link PARAM_DURABLE_SUB_NAME} */ + public static final String PARAM_SUB_DURABLE = "transport.jms.SubscriptionDurable"; + /** The name for the durable subscription See {@link PARAM_SUB_DURABLE}*/ + public static final String PARAM_DURABLE_SUB_NAME = "transport.jms.DurableSubscriberName"; + /** + * JMS Resource cachable level to be used for the service One of the following: + * {@link CACHE_NONE}, {@link CACHE_CONNECTION}, {@link CACHE_SESSION}, {@link CACHE_PRODUCER}, + * {@link CACHE_CONSUMER}, or {@link CACHE_AUTO} - to let the transport decide + */ + public static final String PARAM_CACHE_LEVEL = "transport.jms.CacheLevel"; + /** Should a pub-sub connection receive messages published by itself? */ + public static final String PARAM_PUBSUB_NO_LOCAL = "transport.jms.PubSubNoLocal"; + /** + * The number of milliseconds to wait for a message on a consumer.receive() call + * negative number - wait forever + * 0 - do not wait at all + * positive number - indicates the number of milliseconds to wait + */ + public static final String PARAM_RCV_TIMEOUT = "transport.jms.ReceiveTimeout"; + /** + *The number of concurrent consumers to be created to poll for messages for this service + * For Topics, this should be ONE, to prevent receipt of multiple copies of the same message + */ + public static final String PARAM_CONCURRENT_CONSUMERS = "transport.jms.ConcurrentConsumers"; + /** + * The maximum number of concurrent consumers for the service - See {@link PARAM_CONCURRENT_CONSUMERS} + */ + public static final String PARAM_MAX_CONSUMERS = "transport.jms.MaxConcurrentConsumers"; + /** + * The number of idle (i.e. message-less) polling attempts before a worker task commits suicide, + * to scale down resources, as load decreases + */ + public static final String PARAM_IDLE_TASK_LIMIT = "transport.jms.IdleTaskLimit"; + /** + * The maximum number of messages a polling worker task should process, before suicide - to + * prevent many longer running threads - default is unlimited (i.e. a worker task will live forever) + */ + public static final String PARAM_MAX_MSGS_PER_TASK = "transport.jms.MaxMessagesPerTask"; + /** + * Number of milliseconds before the first reconnection attempt is tried, on detection of an + * error. Subsequent retries follow a geometric series, where the + * duration = previous duration * factor + * This is further limited by the {@link PARAM_RECON_MAX_DURATION} to be meaningful + */ + public static final String PARAM_RECON_INIT_DURATION = "transport.jms.InitialReconnectDuration"; + /** @see PARAM_RECON_INIT_DURATION */ + public static final String PARAM_RECON_FACTOR = "transport.jms.ReconnectProgressFactor"; + /** @see PARAM_RECON_INIT_DURATION */ + public static final String PARAM_RECON_MAX_DURATION = "transport.jms.MaxReconnectDuration"; + + /** The username to use when obtaining a JMS Connection */ + public static final String PARAM_JMS_USERNAME = "transport.jms.UserName"; + /** The password to use when obtaining a JMS Connection */ + public static final String PARAM_JMS_PASSWORD = "transport.jms.Password"; + + //-------------- message context / transport header properties and client options -------------- + /** + * A MessageContext property or client Option indicating the JMS message type + */ + public static final String JMS_MESSAGE_TYPE = "JMS_MESSAGE_TYPE"; + /** + * The message type indicating a BytesMessage. See {@link JMS_MESSAGE_TYPE} + */ + public static final String JMS_BYTE_MESSAGE = "JMS_BYTE_MESSAGE"; + /** + * The message type indicating a TextMessage. See {@link JMS_MESSAGE_TYPE} + */ + public static final String JMS_TEXT_MESSAGE = "JMS_TEXT_MESSAGE"; + /** + * A MessageContext property or client Option indicating the time to wait for a response JMS message + */ + public static final String JMS_WAIT_REPLY = "JMS_WAIT_REPLY"; + /** + * A MessageContext property or client Option indicating the JMS correlation id + */ + public static final String JMS_COORELATION_ID = "JMS_COORELATION_ID"; + /** + * A MessageContext property or client Option indicating the JMS message id + */ + public static final String JMS_MESSAGE_ID = "JMS_MESSAGE_ID"; + /** + * A MessageContext property or client Option indicating the JMS delivery mode as an Integer or String + * Value 1 - javax.jms.DeliveryMode.NON_PERSISTENT + * Value 2 - javax.jms.DeliveryMode.PERSISTENT + */ + public static final String JMS_DELIVERY_MODE = "JMS_DELIVERY_MODE"; + /** + * A MessageContext property or client Option indicating the JMS destination to use on a Send + */ + public static final String JMS_DESTINATION = "JMS_DESTINATION"; + /** + * A MessageContext property or client Option indicating the JMS message expiration - a Long value + * specified as a String + */ + public static final String JMS_EXPIRATION = "JMS_EXPIRATION"; + /** + * A MessageContext property indicating if the message is a redelivery (Boolean as a String) + */ + public static final String JMS_REDELIVERED = "JMS_REDELIVERED"; + /** + * A MessageContext property or client Option indicating the JMS replyTo Destination + */ + public static final String JMS_REPLY_TO = "JMS_REPLY_TO"; + /** + * A MessageContext property or client Option indicating the JMS replyTo Destination type + * See {@link DESTINATION_TYPE_QUEUE} and {@link DESTINATION_TYPE_TOPIC} + */ + public static final String JMS_REPLY_TO_TYPE = "JMS_REPLY_TO_TYPE"; + /** + * A MessageContext property or client Option indicating the JMS timestamp (Long specified as String) + */ + public static final String JMS_TIMESTAMP = "JMS_TIMESTAMP"; + /** + * A MessageContext property indicating the JMS type String returned by {@link javax.jms.Message.getJMSType()} + */ + public static final String JMS_TYPE = "JMS_TYPE"; + /** + * A MessageContext property or client Option indicating the JMS priority + */ + public static final String JMS_PRIORITY = "JMS_PRIORITY"; + /** + * A MessageContext property or client Option indicating the JMS time to live for message sent + */ + public static final String JMS_TIME_TO_LIVE = "JMS_TIME_TO_LIVE"; + + /** The prefix that denotes JMSX properties */ + public static final String JMSX_PREFIX = "JMSX"; + /** The JMSXGroupID property */ + public static final String JMSX_GROUP_ID = "JMSXGroupID"; + /** The JMSXGroupSeq property */ + public static final String JMSX_GROUP_SEQ = "JMSXGroupSeq"; + +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSEndpoint.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSEndpoint.java new file mode 100644 index 0000000000..c465b1d989 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSEndpoint.java @@ -0,0 +1,111 @@ +/* +* Copyright 2004,2005 The Apache Software Foundation. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.jms; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.axis2.addressing.EndpointReference; +import org.apache.axis2.description.AxisService; +import org.apache.axis2.description.Parameter; +import org.apache.tuscany.sca.binding.ws.axis2.jms.ctype.ContentTypeRuleSet; + +/** + * Class that links an Axis2 service to a JMS destination. Additionally, it contains + * all the required information to process incoming JMS messages and to inject them + * into Axis2. + */ +public class JMSEndpoint { + private JMSConnectionFactory cf; + private AxisService service; + private String jndiDestinationName; + private int destinationType = JMSConstants.GENERIC; + private Set<EndpointReference> endpointReferences = new HashSet<EndpointReference>(); + private ContentTypeRuleSet contentTypeRuleSet; + + public AxisService getService() { + return service; + } + + public void setService(AxisService service) { + this.service = service; + } + + public String getServiceName() { + return service.getName(); + } + + public String getJndiDestinationName() { + return jndiDestinationName; + } + + public void setJndiDestinationName(String destinationJNDIName) { + this.jndiDestinationName = destinationJNDIName; + } + + public void setDestinationType(String destinationType) { + if (JMSConstants.DESTINATION_TYPE_TOPIC.equalsIgnoreCase(destinationType)) { + this.destinationType = JMSConstants.TOPIC; + } else if (JMSConstants.DESTINATION_TYPE_QUEUE.equalsIgnoreCase(destinationType)) { + this.destinationType = JMSConstants.QUEUE; + } else { + this.destinationType = JMSConstants.GENERIC; + } + } + + public EndpointReference[] getEndpointReferences() { + return endpointReferences.toArray(new EndpointReference[endpointReferences.size()]); + } + + public void computeEPRs() { + List<EndpointReference> eprs = new ArrayList<EndpointReference>(); + for (Object o : getService().getParameters()) { + Parameter p = (Parameter) o; + if (JMSConstants.PARAM_PUBLISH_EPR.equals(p.getName()) && p.getValue() instanceof String) { + if ("legacy".equalsIgnoreCase((String) p.getValue())) { + // if "legacy" specified, compute and replace it + endpointReferences.add( + new EndpointReference(JMSUtils.getEPR(cf, destinationType, this))); + } else { + endpointReferences.add(new EndpointReference((String) p.getValue())); + } + } + } + + if (eprs.isEmpty()) { + // if nothing specified, compute and return legacy EPR + endpointReferences.add(new EndpointReference(JMSUtils.getEPR(cf, destinationType, this))); + } + } + + public ContentTypeRuleSet getContentTypeRuleSet() { + return contentTypeRuleSet; + } + + public void setContentTypeRuleSet(ContentTypeRuleSet contentTypeRuleSet) { + this.contentTypeRuleSet = contentTypeRuleSet; + } + + public JMSConnectionFactory getCf() { + return cf; + } + + public void setCf(JMSConnectionFactory cf) { + this.cf = cf; + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSExceptionWrapper.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSExceptionWrapper.java new file mode 100644 index 0000000000..ceeec4a6a3 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSExceptionWrapper.java @@ -0,0 +1,28 @@ +/* +* Copyright 2004,2005 The Apache Software Foundation. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.jms; + +import java.io.IOException; + +import javax.jms.JMSException; + +public class JMSExceptionWrapper extends IOException { + private static final long serialVersionUID = 852441109009079511L; + + public JMSExceptionWrapper(JMSException ex) { + initCause(ex); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSListener.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSListener.java new file mode 100644 index 0000000000..8c9f66dfbf --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSListener.java @@ -0,0 +1,294 @@ +/* +* Copyright 2004,2005 The Apache Software Foundation. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.jms; + +import java.util.HashMap; +import java.util.Map; + +import javax.jms.BytesMessage; +import javax.jms.TextMessage; + +import org.apache.axis2.AxisFault; +import org.apache.axis2.Constants; +import org.apache.axis2.addressing.EndpointReference; +import org.apache.axis2.context.ConfigurationContext; +import org.apache.axis2.description.AxisService; +import org.apache.axis2.description.Parameter; +import org.apache.axis2.description.TransportInDescription; +import org.apache.tuscany.sca.binding.ws.axis2.jms.ctype.ContentTypeRuleFactory; +import org.apache.tuscany.sca.binding.ws.axis2.jms.ctype.ContentTypeRuleSet; +import org.apache.tuscany.sca.binding.ws.axis2.jms.ctype.MessageTypeRule; +import org.apache.tuscany.sca.binding.ws.axis2.jms.ctype.PropertyRule; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.AbstractTransportListener; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.BaseConstants; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.ManagementSupport; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.event.TransportErrorListener; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.event.TransportErrorSource; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.event.TransportErrorSourceSupport; + +/** + * The revamped JMS Transport listener implementation. Creates {@link ServiceTaskManager} instances + * for each service requesting exposure over JMS, and stops these if they are undeployed / stopped. + * <p> + * A service indicates a JMS Connection factory definition by name, which would be defined in the + * JMSListner on the axis2.xml, and this provides a way to reuse common configuration between + * services, as well as to optimize resources utilized + * <p> + * If the connection factory name was not specified, it will default to the one named "default" + * {@see JMSConstants.DEFAULT_CONFAC_NAME} + * <p> + * If a destination JNDI name is not specified, a service will expect to use a Queue with the same + * JNDI name as of the service. Additional Parameters allows one to bind to a Topic or specify + * many more detailed control options. See package documentation for more details + * <p> + * All Destinations / JMS Administered objects used MUST be pre-created or already available + */ +public class JMSListener extends AbstractTransportListener implements ManagementSupport, + TransportErrorSource { + + public static final String TRANSPORT_NAME = Constants.TRANSPORT_JMS; + + /** The JMSConnectionFactoryManager which centralizes the management of defined factories */ + private JMSConnectionFactoryManager connFacManager; + /** A Map of service name to the JMS endpoints */ + private Map<String,JMSEndpoint> serviceNameToEndpointMap = new HashMap<String,JMSEndpoint>(); + /** A Map of service name to its ServiceTaskManager instances */ + private Map<String, ServiceTaskManager> serviceNameToSTMMap = + new HashMap<String, ServiceTaskManager>(); + private final TransportErrorSourceSupport tess = new TransportErrorSourceSupport(this); + + /** + * TransportListener initialization + * + * @param cfgCtx the Axis configuration context + * @param trpInDesc the TransportIn description + */ + public void init(ConfigurationContext cfgCtx, + TransportInDescription trpInDesc) throws AxisFault { + + super.init(cfgCtx, trpInDesc); + connFacManager = new JMSConnectionFactoryManager(trpInDesc); + log.info("JMS Transport Receiver/Listener initialized..."); + } + + /** + * Returns EPRs for the given service over the JMS transport + * + * @param serviceName service name + * @return the JMS EPRs for the service + */ + public EndpointReference[] getEPRsForService(String serviceName) { + //Strip out the operation name + if (serviceName.indexOf('/') != -1) { + serviceName = serviceName.substring(0, serviceName.indexOf('/')); + } + // strip out the endpoint name if present + if (serviceName.indexOf('.') != -1) { + serviceName = serviceName.substring(0, serviceName.indexOf('.')); + } + JMSEndpoint endpoint = serviceNameToEndpointMap.get(serviceName); + if (endpoint != null) { + return endpoint.getEndpointReferences(); + } else { + return null; + } + } + + /** + * Listen for JMS messages on behalf of the given service + * + * @param service the Axis service for which to listen for messages + */ + protected void startListeningForService(AxisService service) throws AxisFault { + JMSConnectionFactory cf = getConnectionFactory(service); + if (cf == null) { + throw new AxisFault("The service doesn't specify a JMS connection factory or refers " + + "to an invalid factory."); + } + + JMSEndpoint endpoint = new JMSEndpoint(); + endpoint.setService(service); + endpoint.setCf(cf); + + Parameter destParam = service.getParameter(JMSConstants.PARAM_DESTINATION); + if (destParam != null) { + endpoint.setJndiDestinationName((String)destParam.getValue()); + } else { + // Assume that the JNDI destination name is the same as the service name + endpoint.setJndiDestinationName(service.getName()); + } + + Parameter destTypeParam = service.getParameter(JMSConstants.PARAM_DEST_TYPE); + if (destTypeParam != null) { + String paramValue = (String) destTypeParam.getValue(); + if (JMSConstants.DESTINATION_TYPE_QUEUE.equals(paramValue) || + JMSConstants.DESTINATION_TYPE_TOPIC.equals(paramValue) ) { + endpoint.setDestinationType(paramValue); + } else { + throw new AxisFault("Invalid destinaton type value " + paramValue); + } + } else { + log.debug("JMS destination type not given. default queue"); + endpoint.setDestinationType(JMSConstants.DESTINATION_TYPE_QUEUE); + } + + Parameter contentTypeParam = service.getParameter(JMSConstants.CONTENT_TYPE_PARAM); + if (contentTypeParam == null) { + ContentTypeRuleSet contentTypeRuleSet = new ContentTypeRuleSet(); + contentTypeRuleSet.addRule(new PropertyRule(BaseConstants.CONTENT_TYPE)); + contentTypeRuleSet.addRule(new MessageTypeRule(BytesMessage.class, "application/octet-stream")); + contentTypeRuleSet.addRule(new MessageTypeRule(TextMessage.class, "text/plain")); + endpoint.setContentTypeRuleSet(contentTypeRuleSet); + } else { + endpoint.setContentTypeRuleSet(ContentTypeRuleFactory.parse(contentTypeParam)); + } + + endpoint.computeEPRs(); // compute service EPR and keep for later use + serviceNameToEndpointMap.put(service.getName(), endpoint); + + ServiceTaskManager stm = JMSUtils.createTaskManagerForService(cf, service, workerPool); + stm.setJmsMessageReceiver(new JMSMessageReceiver(this, cf, endpoint)); + stm.start(); + serviceNameToSTMMap.put(service.getName(), stm); + + for (int i=0; i<3; i++) { + if (stm.getActiveTaskCount() > 0) { + log.info("Started to listen on destination : " + stm.getDestinationJNDIName() + + " of type " + JMSUtils.getDestinationTypeAsString(stm.getDestinationType()) + + " for service " + stm.getServiceName()); + return; + } + try { + Thread.sleep(1000); + } catch (InterruptedException ignore) {} + } + + log.warn("Polling tasks on destination : " + stm.getDestinationJNDIName() + + " of type " + JMSUtils.getDestinationTypeAsString(stm.getDestinationType()) + + " for service " + stm.getServiceName() + " have not yet started after 3 seconds .."); + } + + /** + * Stops listening for messages for the service thats undeployed or stopped + * + * @param service the service that was undeployed or stopped + */ + protected void stopListeningForService(AxisService service) { + + ServiceTaskManager stm = serviceNameToSTMMap.get(service.getName()); + if (stm != null) { + if (log.isDebugEnabled()) { + log.debug("Stopping listening on destination : " + stm.getDestinationJNDIName() + + " for service : " + stm.getServiceName()); + } + + stm.stop(); + + serviceNameToSTMMap.remove(service.getName()); + serviceNameToEndpointMap.remove(service.getName()); + log.info("Stopped listening for JMS messages to service : " + service.getName()); + + } else { + log.error("Unable to stop service : " + service.getName() + + " - unable to find its ServiceTaskManager"); + } + } + /** + * Return the connection factory name for this service. If this service + * refers to an invalid factory or defaults to a non-existent default + * factory, this returns null + * + * @param service the AxisService + * @return the JMSConnectionFactory to be used, or null if reference is invalid + */ + public JMSConnectionFactory getConnectionFactory(AxisService service) { + + Parameter conFacParam = service.getParameter(JMSConstants.PARAM_JMS_CONFAC); + // validate connection factory name (specified or default) + if (conFacParam != null) { + return connFacManager.getJMSConnectionFactory((String) conFacParam.getValue()); + } else { + return connFacManager.getJMSConnectionFactory(JMSConstants.DEFAULT_CONFAC_NAME); + } + } + + // -- jmx/management methods-- + /** + * Pause the listener - Stop accepting/processing new messages, but continues processing existing + * messages until they complete. This helps bring an instance into a maintenence mode + * @throws AxisFault on error + */ + public void pause() throws AxisFault { + if (state != BaseConstants.STARTED) return; + try { + for (ServiceTaskManager stm : serviceNameToSTMMap.values()) { + stm.pause(); + } + state = BaseConstants.PAUSED; + log.info("Listener paused"); + } catch (AxisJMSException e) { + log.error("At least one service could not be paused", e); + } + } + + /** + * Resume the lister - Brings the lister into active mode back from a paused state + * @throws AxisFault on error + */ + public void resume() throws AxisFault { + if (state != BaseConstants.PAUSED) return; + try { + for (ServiceTaskManager stm : serviceNameToSTMMap.values()) { + stm.resume(); + } + state = BaseConstants.STARTED; + log.info("Listener resumed"); + } catch (AxisJMSException e) { + log.error("At least one service could not be resumed", e); + } + } + + /** + * Stop processing new messages, and wait the specified maximum time for in-flight + * requests to complete before a controlled shutdown for maintenence + * + * @param millis a number of milliseconds to wait until pending requests are allowed to complete + * @throws AxisFault on error + */ + public void maintenenceShutdown(long millis) throws AxisFault { + if (state != BaseConstants.STARTED) return; + try { + long start = System.currentTimeMillis(); + stop(); + state = BaseConstants.STOPPED; + log.info("Listener shutdown in : " + (System.currentTimeMillis() - start) / 1000 + "s"); + } catch (Exception e) { + handleException("Error shutting down the listener for maintenence", e); + } + } + + public void addErrorListener(TransportErrorListener listener) { + tess.addErrorListener(listener); + } + + public void removeErrorListener(TransportErrorListener listener) { + tess.removeErrorListener(listener); + } + + void error(AxisService service, Throwable ex) { + tess.error(service, ex); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSMessageReceiver.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSMessageReceiver.java new file mode 100644 index 0000000000..ebd67e53e1 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSMessageReceiver.java @@ -0,0 +1,237 @@ +/* +* Copyright 2004,2005 The Apache Software Foundation. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.jms; + +import javax.jms.DeliveryMode; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.TextMessage; +import javax.transaction.UserTransaction; +import javax.xml.namespace.QName; + +import org.apache.axis2.AxisFault; +import org.apache.axis2.Constants; +import org.apache.axis2.context.MessageContext; +import org.apache.axis2.description.AxisOperation; +import org.apache.axis2.description.AxisService; +import org.apache.axis2.description.Parameter; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.tuscany.sca.binding.ws.axis2.jms.ctype.ContentTypeInfo; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.BaseConstants; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.BaseUtils; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.MetricsCollector; + +/** + * This is the JMS message receiver which is invoked when a message is received. This processes + * the message through the engine + */ +public class JMSMessageReceiver { + + private static final Log log = LogFactory.getLog(JMSMessageReceiver.class); + + /** The JMSListener */ + private JMSListener jmsListener = null; + /** A reference to the JMS Connection Factory */ + private JMSConnectionFactory jmsConnectionFactory = null; + /** The JMS metrics collector */ + private MetricsCollector metrics = null; + /** The endpoint this message receiver is bound to */ + final JMSEndpoint endpoint; + + /** + * Create a new JMSMessage receiver + * + * @param jmsListener the JMS transport Listener + * @param jmsConFac the JMS connection factory we are associated with + * @param workerPool the worker thread pool to be used + * @param cfgCtx the axis ConfigurationContext + * @param serviceName the name of the Axis service + * @param endpoint the JMSEndpoint definition to be used + */ + JMSMessageReceiver(JMSListener jmsListener, JMSConnectionFactory jmsConFac, JMSEndpoint endpoint) { + this.jmsListener = jmsListener; + this.jmsConnectionFactory = jmsConFac; + this.endpoint = endpoint; + this.metrics = jmsListener.getMetricsCollector(); + } + + /** + * Process a new message received + * + * @param message the JMS message received + * @param ut UserTransaction which was used to receive the message + * @return true if caller should commit + */ + public boolean onMessage(Message message, UserTransaction ut) { + + try { + if (log.isDebugEnabled()) { + StringBuffer sb = new StringBuffer(); + sb.append("Received new JMS message for service :").append(endpoint.getServiceName()); + sb.append("\nDestination : ").append(message.getJMSDestination()); + sb.append("\nMessage ID : ").append(message.getJMSMessageID()); + sb.append("\nCorrelation ID : ").append(message.getJMSCorrelationID()); + sb.append("\nReplyTo : ").append(message.getJMSReplyTo()); + sb.append("\nRedelivery ? : ").append(message.getJMSRedelivered()); + sb.append("\nPriority : ").append(message.getJMSPriority()); + sb.append("\nExpiration : ").append(message.getJMSExpiration()); + sb.append("\nTimestamp : ").append(message.getJMSTimestamp()); + sb.append("\nMessage Type : ").append(message.getJMSType()); + sb.append("\nPersistent ? : ").append( + DeliveryMode.PERSISTENT == message.getJMSDeliveryMode()); + + log.debug(sb.toString()); + if (log.isTraceEnabled() && message instanceof TextMessage) { + log.trace("\nMessage : " + ((TextMessage) message).getText()); + } + } + } catch (JMSException e) { + if (log.isDebugEnabled()) { + log.debug("Error reading JMS message headers for debug logging", e); + } + } + + // update transport level metrics + try { + metrics.incrementBytesReceived(JMSUtils.getMessageSize(message)); + } catch (JMSException e) { + log.warn("Error reading JMS message size to update transport metrics", e); + } + + // has this message already expired? expiration time == 0 means never expires + try { + long expiryTime = message.getJMSExpiration(); + if (expiryTime > 0 && System.currentTimeMillis() > expiryTime) { + if (log.isDebugEnabled()) { + log.debug("Discard expired message with ID : " + message.getJMSMessageID()); + } + return true; + } + } catch (JMSException ignore) {} + + + boolean successful = false; + try { + successful = processThoughEngine(message, ut); + + } catch (JMSException e) { + log.error("JMS Exception encountered while processing", e); + } catch (AxisFault e) { + log.error("Axis fault processing message", e); + } catch (Exception e) { + log.error("Unknown error processing message", e); + + } finally { + if (successful) { + metrics.incrementMessagesReceived(); + } else { + metrics.incrementFaultsReceiving(); + } + } + + return successful; + } + + /** + * Process the new message through Axis2 + * + * @param message the JMS message + * @param ut the UserTransaction used for receipt + * @return true if the caller should commit + * @throws JMSException, on JMS exceptions + * @throws AxisFault on Axis2 errors + */ + private boolean processThoughEngine(Message message, UserTransaction ut) + throws JMSException, AxisFault { + + MessageContext msgContext = jmsListener.createMessageContext(); + + // set the JMS Message ID as the Message ID of the MessageContext + try { + msgContext.setMessageID(message.getJMSMessageID()); + msgContext.setProperty(JMSConstants.JMS_COORELATION_ID, message.getJMSMessageID()); + } catch (JMSException ignore) {} + + String soapAction = JMSUtils.getProperty(message, BaseConstants.SOAPACTION); + + AxisService service = endpoint.getService(); + msgContext.setAxisService(service); + + // find the operation for the message, or default to one + Parameter operationParam = service.getParameter(BaseConstants.OPERATION_PARAM); + QName operationQName = ( + operationParam != null ? + BaseUtils.getQNameFromString(operationParam.getValue()) : + BaseConstants.DEFAULT_OPERATION); + + AxisOperation operation = service.getOperation(operationQName); + if (operation != null) { + msgContext.setAxisOperation(operation); + msgContext.setSoapAction("urn:" + operation.getName().getLocalPart()); + } + + ContentTypeInfo contentTypeInfo = + endpoint.getContentTypeRuleSet().getContentTypeInfo(message); + if (contentTypeInfo == null) { + throw new AxisFault("Unable to determine content type for message " + + msgContext.getMessageID()); + } + + // set the message property OUT_TRANSPORT_INFO + // the reply is assumed to be over the JMSReplyTo destination, using + // the same incoming connection factory, if a JMSReplyTo is available + Destination replyTo = message.getJMSReplyTo(); + if (replyTo == null) { + // does the service specify a default reply destination ? + Parameter param = service.getParameter(JMSConstants.PARAM_REPLY_DESTINATION); + if (param != null && param.getValue() != null) { + replyTo = jmsConnectionFactory.getDestination((String) param.getValue()); + } + + } + if (replyTo != null) { + msgContext.setProperty(Constants.OUT_TRANSPORT_INFO, + new JMSOutTransportInfo(jmsConnectionFactory, replyTo, + contentTypeInfo.getPropertyName())); + } + + JMSUtils.setSOAPEnvelope(message, msgContext, contentTypeInfo.getContentType()); + if (ut != null) { + msgContext.setProperty(BaseConstants.USER_TRANSACTION, ut); + } + + try { + jmsListener.handleIncomingMessage( + msgContext, + JMSUtils.getTransportHeaders(message), + soapAction, + contentTypeInfo.getContentType()); + + } finally { + + Object o = msgContext.getProperty(BaseConstants.SET_ROLLBACK_ONLY); + if (o != null) { + if ((o instanceof Boolean && ((Boolean) o)) || + (o instanceof String && Boolean.valueOf((String) o))) { + return false; + } + } + return true; + } + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSMessageSender.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSMessageSender.java new file mode 100644 index 0000000000..01fdee77dd --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSMessageSender.java @@ -0,0 +1,332 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.jms; + +import javax.jms.Connection; +import javax.jms.DeliveryMode; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageProducer; +import javax.jms.QueueSender; +import javax.jms.Session; +import javax.jms.TopicPublisher; +import javax.transaction.UserTransaction; + +import org.apache.axis2.context.MessageContext; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.BaseConstants; + +/** + * Performs the actual sending of a JMS message, and the subsequent committing of a JTA transaction + * (if requested) or the local session transaction, if used. An instance of this class is unique + * to a single message send out operation and will not be shared. + */ +public class JMSMessageSender { + + private static final Log log = LogFactory.getLog(JMSMessageSender.class); + + /** The Connection to be used to send out */ + private Connection connection = null; + /** The Session to be used to send out */ + private Session session = null; + /** The MessageProducer used */ + private MessageProducer producer = null; + /** Target Destination */ + private Destination destination = null; + /** The level of cachability for resources */ + private int cacheLevel = JMSConstants.CACHE_CONNECTION; + /** Should this sender use JMS 1.1 ? (if false, defaults to 1.0.2b) */ + private boolean jmsSpec11 = true; + /** Are we sending to a Queue ? */ + private Boolean isQueue = null; + + /** + * This is a low-end method to support the one-time sends using JMS 1.0.2b + * @param connection the JMS Connection + * @param session JMS Session + * @param producer the MessageProducer + * @param destination the JMS Destination + * @param cacheLevel cacheLevel - None | Connection | Session | Producer + * @param jmsSpec11 true if the JMS 1.1 API should be used + * @param isQueue posting to a Queue? + */ + public JMSMessageSender(Connection connection, Session session, MessageProducer producer, + Destination destination, int cacheLevel, boolean jmsSpec11, Boolean isQueue) { + + this.connection = connection; + this.session = session; + this.producer = producer; + this.destination = destination; + this.cacheLevel = cacheLevel; + this.jmsSpec11 = jmsSpec11; + this.isQueue = isQueue; + } + + /** + * Create a JMSSender using a JMSConnectionFactory and target EPR + * + * @param jmsConnectionFactory the JMSConnectionFactory + * @param targetAddress target EPR + */ + public JMSMessageSender(JMSConnectionFactory jmsConnectionFactory, String targetAddress) { + + if (jmsConnectionFactory != null) { + this.cacheLevel = jmsConnectionFactory.getCacheLevel(); + this.jmsSpec11 = jmsConnectionFactory.isJmsSpec11(); + this.connection = jmsConnectionFactory.getConnection(); + this.session = jmsConnectionFactory.getSession(connection); + this.destination = + jmsConnectionFactory.getSharedDestination() == null ? + jmsConnectionFactory.getDestination(JMSUtils.getDestination(targetAddress)) : + jmsConnectionFactory.getSharedDestination(); + this.producer = jmsConnectionFactory.getMessageProducer(connection, session, destination); + + } else { + JMSOutTransportInfo jmsOut = new JMSOutTransportInfo(targetAddress); + jmsOut.loadConnectionFactoryFromProperies(); + } + } + + /** + * Perform actual send of JMS message to the Destination selected + * + * @param message the JMS message + * @param msgCtx the Axis2 MessageContext + */ + public void send(Message message, MessageContext msgCtx) { + + Boolean jtaCommit = getBooleanProperty(msgCtx, BaseConstants.JTA_COMMIT_AFTER_SEND); + Boolean rollbackOnly = getBooleanProperty(msgCtx, BaseConstants.SET_ROLLBACK_ONLY); + Boolean persistent = getBooleanProperty(msgCtx, JMSConstants.JMS_DELIVERY_MODE); + Integer priority = getIntegerProperty(msgCtx, JMSConstants.JMS_PRIORITY); + Integer timeToLive = getIntegerProperty(msgCtx, JMSConstants.JMS_TIME_TO_LIVE); + + // Do not commit, if message is marked for rollback + if (rollbackOnly != null && rollbackOnly) { + jtaCommit = Boolean.FALSE; + } + + if (persistent != null) { + try { + producer.setDeliveryMode(DeliveryMode.PERSISTENT); + } catch (JMSException e) { + handleException("Error setting JMS Producer for PERSISTENT delivery", e); + } + } + if (priority != null) { + try { + producer.setPriority(priority); + } catch (JMSException e) { + handleException("Error setting JMS Producer priority to : " + priority, e); + } + } + if (timeToLive != null) { + try { + producer.setTimeToLive(timeToLive); + } catch (JMSException e) { + handleException("Error setting JMS Producer TTL to : " + timeToLive, e); + } + } + + boolean sendingSuccessful = false; + // perform actual message sending + try { + if (jmsSpec11 || isQueue == null) { + producer.send(message); + + } else { + if (isQueue) { + ((QueueSender) producer).send(message); + + } else { + ((TopicPublisher) producer).publish(message); + } + } + + // set the actual MessageID to the message context for use by any others down the line + String msgId = null; + try { + msgId = message.getJMSMessageID(); + if (msgId != null) { + msgCtx.setProperty(JMSConstants.JMS_MESSAGE_ID, msgId); + } + } catch (JMSException ignore) {} + + sendingSuccessful = true; + + if (log.isDebugEnabled()) { + log.debug("Sent Message Context ID : " + msgCtx.getMessageID() + + " with JMS Message ID : " + msgId + + " to destination : " + producer.getDestination()); + } + + } catch (JMSException e) { + log.error("Error sending message with MessageContext ID : " + + msgCtx.getMessageID() + " to destination : " + destination, e); + + } finally { + + if (jtaCommit != null) { + + UserTransaction ut = (UserTransaction) msgCtx.getProperty(BaseConstants.USER_TRANSACTION); + if (ut != null) { + + try { + if (sendingSuccessful && jtaCommit) { + ut.commit(); + } else { + ut.rollback(); + } + msgCtx.removeProperty(BaseConstants.USER_TRANSACTION); + + if (log.isDebugEnabled()) { + log.debug((sendingSuccessful ? "Committed" : "Rolled back") + + " JTA Transaction"); + } + + } catch (Exception e) { + handleException("Error committing/rolling back JTA transaction after " + + "sending of message with MessageContext ID : " + msgCtx.getMessageID() + + " to destination : " + destination, e); + } + } + + } else { + try { + if (session.getTransacted()) { + if (sendingSuccessful && (rollbackOnly == null || !rollbackOnly)) { + session.commit(); + } else { + session.rollback(); + } + } + + if (log.isDebugEnabled()) { + log.debug((sendingSuccessful ? "Committed" : "Rolled back") + + " local (JMS Session) Transaction"); + } + + } catch (JMSException e) { + handleException("Error committing/rolling back local (i.e. session) " + + "transaction after sending of message with MessageContext ID : " + + msgCtx.getMessageID() + " to destination : " + destination, e); + } + } + } + } + + /** + * Close non-shared producer, session and connection if any + */ + public void close() { + if (producer != null && cacheLevel < JMSConstants.CACHE_PRODUCER) { + try { + producer.close(); + } catch (JMSException e) { + log.error("Error closing JMS MessageProducer after send", e); + } finally { + producer = null; + } + } + + if (session != null && cacheLevel < JMSConstants.CACHE_SESSION) { + try { + session.close(); + } catch (JMSException e) { + log.error("Error closing JMS Session after send", e); + } finally { + session = null; + } + } + + if (connection != null && cacheLevel < JMSConstants.CACHE_CONNECTION) { + try { + connection.close(); + } catch (JMSException e) { + log.error("Error closing JMS Connection after send", e); + } finally { + connection = null; + } + } + } + + private void handleException(String message, Exception e) { + log.error(message, e); + throw new AxisJMSException(message, e); + } + + private Boolean getBooleanProperty(MessageContext msgCtx, String name) { + Object o = msgCtx.getProperty(name); + if (o != null) { + if (o instanceof Boolean) { + return (Boolean) o; + } else if (o instanceof String) { + return Boolean.valueOf((String) o); + } + } + return null; + } + + private Integer getIntegerProperty(MessageContext msgCtx, String name) { + Object o = msgCtx.getProperty(name); + if (o != null) { + if (o instanceof Integer) { + return (Integer) o; + } else if (o instanceof String) { + return Integer.parseInt((String) o); + } + } + return null; + } + + public void setConnection(Connection connection) { + this.connection = connection; + } + + public void setSession(Session session) { + this.session = session; + } + + public void setProducer(MessageProducer producer) { + this.producer = producer; + } + + public void setCacheLevel(int cacheLevel) { + this.cacheLevel = cacheLevel; + } + + public int getCacheLevel() { + return cacheLevel; + } + + public Connection getConnection() { + return connection; + } + + public MessageProducer getProducer() { + return producer; + } + + public Session getSession() { + return session; + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSOutTransportInfo.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSOutTransportInfo.java new file mode 100644 index 0000000000..9e029b33e1 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSOutTransportInfo.java @@ -0,0 +1,306 @@ +/* +* Copyright 2004,2005 The Apache Software Foundation. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.jms; + +import java.util.Hashtable; + +import javax.jms.ConnectionFactory; +import javax.jms.Destination; +import javax.jms.Topic; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NameNotFoundException; +import javax.naming.NamingException; + +import org.apache.axis2.transport.OutTransportInfo; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.BaseUtils; + +/** + * The JMS OutTransportInfo is a holder of information to send an outgoing message + * (e.g. a Response) to a JMS destination. Thus at a minimum a reference to a + * ConnectionFactory and a Destination are held + */ +public class JMSOutTransportInfo implements OutTransportInfo { + + private static final Log log = LogFactory.getLog(JMSOutTransportInfo.class); + + /** The naming context */ + private Context context; + /** + * this is a reference to the underlying JMS ConnectionFactory when sending messages + * through connection factories not defined at the TransportSender level + */ + private ConnectionFactory connectionFactory = null; + /** + * this is a reference to a JMS Connection Factory instance, which has a reference + * to the underlying actual connection factory, an open connection to the JMS provider + * and optionally a session already available for use + */ + private JMSConnectionFactory jmsConnectionFactory = null; + /** the Destination queue or topic for the outgoing message */ + private Destination destination = null; + /** the Destination queue or topic for the outgoing message + * i.e. JMSConstants.DESTINATION_TYPE_QUEUE, DESTINATION_TYPE_TOPIC or DESTINATION_TYPE_GENERIC + */ + private String destinationType = JMSConstants.DESTINATION_TYPE_GENERIC; + /** the Reply Destination queue or topic for the outgoing message */ + private Destination replyDestination = null; + /** the Reply Destination name */ + private String replyDestinationName = null; + /** the Reply Destination queue or topic for the outgoing message + * i.e. JMSConstants.DESTINATION_TYPE_QUEUE, DESTINATION_TYPE_TOPIC or DESTINATION_TYPE_GENERIC + */ + private String replyDestinationType = JMSConstants.DESTINATION_TYPE_GENERIC; + /** the EPR properties when the out-transport info is generated from a target EPR */ + private Hashtable<String,String> properties = null; + /** the target EPR string where applicable */ + private String targetEPR = null; + /** the message property name that stores the content type of the outgoing message */ + private String contentTypeProperty; + + /** + * Creates an instance using the given JMS connection factory and destination + * + * @param jmsConnectionFactory the JMS connection factory + * @param dest the destination + * @param contentTypeProperty + */ + JMSOutTransportInfo(JMSConnectionFactory jmsConnectionFactory, Destination dest, + String contentTypeProperty) { + this.jmsConnectionFactory = jmsConnectionFactory; + this.destination = dest; + destinationType = dest instanceof Topic ? JMSConstants.DESTINATION_TYPE_TOPIC + : JMSConstants.DESTINATION_TYPE_QUEUE; + this.contentTypeProperty = contentTypeProperty; + } + + /** + * Creates and instance using the given URL + * + * @param targetEPR the target EPR + */ + JMSOutTransportInfo(String targetEPR) { + + this.targetEPR = targetEPR; + if (!targetEPR.startsWith(JMSConstants.JMS_PREFIX)) { + handleException("Invalid prefix for a JMS EPR : " + targetEPR); + + } else { + properties = BaseUtils.getEPRProperties(targetEPR); + String destinationType = properties.get(JMSConstants.PARAM_DEST_TYPE); + if (destinationType != null) { + setDestinationType(destinationType); + } + + String replyDestinationType = properties.get(JMSConstants.PARAM_REPLY_DEST_TYPE); + if (replyDestinationType != null) { + setReplyDestinationType(replyDestinationType); + } + + replyDestinationName = properties.get(JMSConstants.PARAM_REPLY_DESTINATION); + contentTypeProperty = properties.get(JMSConstants.CONTENT_TYPE_PROPERTY_PARAM); + try { + context = new InitialContext(properties); + } catch (NamingException e) { + handleException("Could not get an initial context using " + properties, e); + } + + destination = getDestination(context, targetEPR); + replyDestination = getReplyDestination(context, targetEPR); + } + } + + /** + * Provides a lazy load when created with a target EPR. This method performs actual + * lookup for the connection factory and destination + */ + public void loadConnectionFactoryFromProperies() { + if (properties != null) { + connectionFactory = getConnectionFactory(context, properties); + } + } + + /** + * Get the referenced ConnectionFactory using the properties from the context + * + * @param context the context to use for lookup + * @param props the properties which contains the JNDI name of the factory + * @return the connection factory + */ + private ConnectionFactory getConnectionFactory(Context context, Hashtable<String,String> props) { + try { + + String conFacJndiName = props.get(JMSConstants.PARAM_CONFAC_JNDI_NAME); + if (conFacJndiName != null) { + return JMSUtils.lookup(context, ConnectionFactory.class, conFacJndiName); + } else { + handleException("Connection Factory JNDI name cannot be determined"); + } + } catch (NamingException e) { + handleException("Failed to look up connection factory from JNDI", e); + } + return null; + } + + /** + * Get the JMS destination specified by the given URL from the context + * + * @param context the Context to lookup + * @param url URL + * @return the JMS destination, or null if it does not exist + */ + private Destination getDestination(Context context, String url) { + String destinationName = JMSUtils.getDestination(url); + try { + return JMSUtils.lookup(context, Destination.class, destinationName); + } catch (NameNotFoundException e) { + try { + return JMSUtils.lookup(context, Destination.class, + (JMSConstants.DESTINATION_TYPE_TOPIC.equals(destinationType) ? + "dynamicTopics/" : "dynamicQueues/") + destinationName); + } catch (NamingException x) { + handleException("Cannot locate destination : " + destinationName + " using " + url); + } + } catch (NamingException e) { + handleException("Cannot locate destination : " + destinationName + " using " + url, e); + } + return null; + } + + /** + * Get the JMS reply destination specified by the given URL from the context + * + * @param context the Context to lookup + * @param url URL + * @return the JMS destination, or null if it does not exist + */ + private Destination getReplyDestination(Context context, String url) { + String replyDestinationName = properties.get(JMSConstants.PARAM_REPLY_DESTINATION); + if(replyDestinationName == null) { + return null; + } + + try { + return JMSUtils.lookup(context, Destination.class, replyDestinationName); + } catch (NameNotFoundException e) { + if (log.isDebugEnabled()) { + log.debug("Cannot locate destination : " + replyDestinationName + " using " + url); + } + } catch (NamingException e) { + handleException("Cannot locate destination : " + replyDestinationName + " using " + url, e); + } + + return null; + } + + /** + * Look up for the given destination + * @param replyDest the JNDI name to lookup Destination required + * @return Destination for the JNDI name passed + */ + public Destination getReplyDestination(String replyDest) { + try { + return JMSUtils.lookup(jmsConnectionFactory.getContext(), Destination.class, + replyDest); + } catch (NameNotFoundException e) { + if (log.isDebugEnabled()) { + log.debug("Cannot locate reply destination : " + replyDest, e); + } + } catch (NamingException e) { + handleException("Cannot locate reply destination : " + replyDest, e); + } + return null; + } + + + private void handleException(String s) { + log.error(s); + throw new AxisJMSException(s); + } + + private void handleException(String s, Exception e) { + log.error(s, e); + throw new AxisJMSException(s, e); + } + + public Destination getDestination() { + return destination; + } + + public ConnectionFactory getConnectionFactory() { + return connectionFactory; + } + + public JMSConnectionFactory getJmsConnectionFactory() { + return jmsConnectionFactory; + } + + public void setContentType(String contentType) { + // this is a useless Axis2 method imposed by the OutTransportInfo interface :( + } + + public Hashtable<String,String> getProperties() { + return properties; + } + + public String getTargetEPR() { + return targetEPR; + } + + public String getDestinationType() { + return destinationType; + } + + public void setDestinationType(String destinationType) { + if (destinationType != null) { + this.destinationType = destinationType; + } + } + + public Destination getReplyDestination() { + return replyDestination; + } + + public void setReplyDestination(Destination replyDestination) { + this.replyDestination = replyDestination; + } + + public String getReplyDestinationType() { + return replyDestinationType; + } + + public void setReplyDestinationType(String replyDestinationType) { + this.replyDestinationType = replyDestinationType; + } + + public String getReplyDestinationName() { + return replyDestinationName; + } + + public void setReplyDestinationName(String replyDestinationName) { + this.replyDestinationName = replyDestinationName; + } + + public String getContentTypeProperty() { + return contentTypeProperty; + } + + public void setContentTypeProperty(String contentTypeProperty) { + this.contentTypeProperty = contentTypeProperty; + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSSender.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSSender.java new file mode 100644 index 0000000000..a5f77dc4c9 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSSender.java @@ -0,0 +1,499 @@ +/* +* Copyright 2004,2005 The Apache Software Foundation. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.jms; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.StringWriter; +import java.nio.charset.UnsupportedCharsetException; +import java.util.Map; + +import javax.activation.DataHandler; +import javax.jms.BytesMessage; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.Session; +import javax.jms.TextMessage; + +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMNode; +import org.apache.axiom.om.OMOutputFormat; +import org.apache.axiom.om.OMText; +import org.apache.axis2.AxisFault; +import org.apache.axis2.Constants; +import org.apache.axis2.context.ConfigurationContext; +import org.apache.axis2.context.MessageContext; +import org.apache.axis2.description.TransportOutDescription; +import org.apache.axis2.transport.MessageFormatter; +import org.apache.axis2.transport.OutTransportInfo; +import org.apache.axis2.transport.TransportUtils; +import org.apache.axis2.transport.http.HTTPConstants; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.AbstractTransportSender; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.BaseConstants; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.BaseUtils; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.ManagementSupport; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.streams.WriterOutputStream; + +/** + * The TransportSender for JMS + */ +public class JMSSender extends AbstractTransportSender implements ManagementSupport { + + public static final String TRANSPORT_NAME = Constants.TRANSPORT_JMS; + + /** The JMS connection factory manager to be used when sending messages out */ + private JMSConnectionFactoryManager connFacManager; + + /** + * Initialize the transport sender by reading pre-defined connection factories for + * outgoing messages. + * + * @param cfgCtx the configuration context + * @param transportOut the transport sender definition from axis2.xml + * @throws AxisFault on error + */ + public void init(ConfigurationContext cfgCtx, TransportOutDescription transportOut) throws AxisFault { + super.init(cfgCtx, transportOut); + connFacManager = new JMSConnectionFactoryManager(transportOut); + log.info("JMS Transport Sender initialized..."); + } + + /** + * Get corresponding JMS connection factory defined within the transport sender for the + * transport-out information - usually constructed from a targetEPR + * + * @param trpInfo the transport-out information + * @return the corresponding JMS connection factory, if any + */ + private JMSConnectionFactory getJMSConnectionFactory(JMSOutTransportInfo trpInfo) { + Map<String,String> props = trpInfo.getProperties(); + if (trpInfo.getProperties() != null) { + String jmsConnectionFactoryName = props.get(JMSConstants.PARAM_JMS_CONFAC); + if (jmsConnectionFactoryName != null) { + return connFacManager.getJMSConnectionFactory(jmsConnectionFactoryName); + } else { + return connFacManager.getJMSConnectionFactory(props); + } + } else { + return null; + } + } + + /** + * Performs the actual sending of the JMS message + */ + public void sendMessage(MessageContext msgCtx, String targetAddress, + OutTransportInfo outTransportInfo) throws AxisFault { + + JMSConnectionFactory jmsConnectionFactory = null; + JMSOutTransportInfo jmsOut = null; + JMSMessageSender messageSender = null; + + if (targetAddress != null) { + + jmsOut = new JMSOutTransportInfo(targetAddress); + // do we have a definition for a connection factory to use for this address? + jmsConnectionFactory = getJMSConnectionFactory(jmsOut); + + if (jmsConnectionFactory != null) { + messageSender = new JMSMessageSender(jmsConnectionFactory, targetAddress); + + } else { + try { + messageSender = JMSUtils.createJMSSender(jmsOut); + } catch (JMSException e) { + handleException("Unable to create a JMSMessageSender for : " + outTransportInfo, e); + } + } + + } else if (outTransportInfo != null && outTransportInfo instanceof JMSOutTransportInfo) { + + jmsOut = (JMSOutTransportInfo) outTransportInfo; + try { + messageSender = JMSUtils.createJMSSender(jmsOut); + } catch (JMSException e) { + handleException("Unable to create a JMSMessageSender for : " + outTransportInfo, e); + } + } + + // The message property to be used to send the content type is determined by + // the out transport info, i.e. either from the EPR if we are sending a request, + // or, if we are sending a response, from the configuration of the service that + // received the request). The property name can be overridden by a message + // context property. + String contentTypeProperty = + (String) msgCtx.getProperty(JMSConstants.CONTENT_TYPE_PROPERTY_PARAM); + if (contentTypeProperty == null) { + contentTypeProperty = jmsOut.getContentTypeProperty(); + } + + // need to synchronize as Sessions are not thread safe + synchronized (messageSender.getSession()) { + try { + sendOverJMS(msgCtx, messageSender, contentTypeProperty, jmsConnectionFactory, jmsOut); + } finally { + messageSender.close(); + } + } + } + + /** + * Perform actual sending of the JMS message + */ + private void sendOverJMS(MessageContext msgCtx, JMSMessageSender messageSender, + String contentTypeProperty, JMSConnectionFactory jmsConnectionFactory, + JMSOutTransportInfo jmsOut) throws AxisFault { + + // convert the axis message context into a JMS Message that we can send over JMS + Message message = null; + String correlationId = null; + try { + message = createJMSMessage(msgCtx, messageSender.getSession(), contentTypeProperty); + } catch (JMSException e) { + handleException("Error creating a JMS message from the message context", e); + } + + // should we wait for a synchronous response on this same thread? + boolean waitForResponse = waitForSynchronousResponse(msgCtx); + Destination replyDestination = jmsOut.getReplyDestination(); + + // if this is a synchronous out-in, prepare to listen on the response destination + if (waitForResponse) { + + String replyDestName = (String) msgCtx.getProperty(JMSConstants.JMS_REPLY_TO); + if (replyDestName == null && jmsConnectionFactory != null) { + replyDestName = jmsConnectionFactory.getReplyToDestination(); + } + + if (replyDestName != null) { + if (jmsConnectionFactory != null) { + replyDestination = jmsConnectionFactory.getDestination(replyDestName); + } else { + replyDestination = jmsOut.getReplyDestination(replyDestName); + } + } + replyDestination = JMSUtils.setReplyDestination( + replyDestination, messageSender.getSession(), message); + } + + try { + messageSender.send(message, msgCtx); + metrics.incrementMessagesSent(msgCtx); + + } catch (AxisJMSException e) { + metrics.incrementFaultsSending(); + handleException("Error sending JMS message", e); + } + + try { + metrics.incrementBytesSent(msgCtx, JMSUtils.getMessageSize(message)); + } catch (JMSException e) { + log.warn("Error reading JMS message size to update transport metrics", e); + } + + // if we are expecting a synchronous response back for the message sent out + if (waitForResponse) { + // TODO ******************************************************************************** + // TODO **** replace with asynchronous polling via a poller task to process this ******* + // information would be given. Then it should poll (until timeout) the + // requested destination for the response message and inject it from a + // asynchronous worker thread + try { + messageSender.getConnection().start(); // multiple calls are safely ignored + } catch (JMSException ignore) {} + + try { + correlationId = message.getJMSMessageID(); + } catch(JMSException ignore) {} + + // We assume here that the response uses the same message property to + // specify the content type of the message. + waitForResponseAndProcess(messageSender.getSession(), replyDestination, + msgCtx, correlationId, contentTypeProperty); + // TODO ******************************************************************************** + } + } + + /** + * Create a Consumer for the reply destination and wait for the response JMS message + * synchronously. If a message arrives within the specified time interval, process it + * through Axis2 + * @param session the session to use to listen for the response + * @param replyDestination the JMS reply Destination + * @param msgCtx the outgoing message for which we are expecting the response + * @param contentTypeProperty the message property used to determine the content type + * of the response message + * @throws AxisFault on error + */ + private void waitForResponseAndProcess(Session session, Destination replyDestination, + MessageContext msgCtx, String correlationId, + String contentTypeProperty) throws AxisFault { + + try { + MessageConsumer consumer; + consumer = JMSUtils.createConsumer(session, replyDestination, + "JMSCorrelationID = '" + correlationId + "'"); + + // how long are we willing to wait for the sync response + long timeout = JMSConstants.DEFAULT_JMS_TIMEOUT; + String waitReply = (String) msgCtx.getProperty(JMSConstants.JMS_WAIT_REPLY); + if (waitReply != null) { + timeout = Long.valueOf(waitReply).longValue(); + } + + if (log.isDebugEnabled()) { + log.debug("Waiting for a maximum of " + timeout + + "ms for a response message to destination : " + replyDestination + + " with JMS correlation ID : " + correlationId); + } + + Message reply = consumer.receive(timeout); + + if (reply != null) { + + // update transport level metrics + metrics.incrementMessagesReceived(); + try { + metrics.incrementBytesReceived(JMSUtils.getMessageSize(reply)); + } catch (JMSException e) { + log.warn("Error reading JMS message size to update transport metrics", e); + } + + try { + processSyncResponse(msgCtx, reply, contentTypeProperty); + metrics.incrementMessagesReceived(); + } catch (AxisFault e) { + metrics.incrementFaultsReceiving(); + throw e; + } + + } else { + log.warn("Did not receive a JMS response within " + + timeout + " ms to destination : " + replyDestination + + " with JMS correlation ID : " + correlationId); + metrics.incrementTimeoutsReceiving(); + } + + } catch (JMSException e) { + metrics.incrementFaultsReceiving(); + handleException("Error creating a consumer, or receiving a synchronous reply " + + "for outgoing MessageContext ID : " + msgCtx.getMessageID() + + " and reply Destination : " + replyDestination, e); + } + } + + /** + * Create a JMS Message from the given MessageContext and using the given + * session + * + * @param msgContext the MessageContext + * @param session the JMS session + * @param contentTypeProperty the message property to be used to store the + * content type + * @return a JMS message from the context and session + * @throws JMSException on exception + * @throws AxisFault on exception + */ + private Message createJMSMessage(MessageContext msgContext, Session session, + String contentTypeProperty) throws JMSException, AxisFault { + + Message message = null; + String msgType = getProperty(msgContext, JMSConstants.JMS_MESSAGE_TYPE); + + // check the first element of the SOAP body, do we have content wrapped using the + // default wrapper elements for binary (BaseConstants.DEFAULT_BINARY_WRAPPER) or + // text (BaseConstants.DEFAULT_TEXT_WRAPPER) ? If so, do not create SOAP messages + // for JMS but just get the payload in its native format + String jmsPayloadType = guessMessageType(msgContext); + + if (jmsPayloadType == null) { + + OMOutputFormat format = BaseUtils.getOMOutputFormat(msgContext); + MessageFormatter messageFormatter = null; + try { + messageFormatter = TransportUtils.getMessageFormatter(msgContext); + } catch (AxisFault axisFault) { + throw new JMSException("Unable to get the message formatter to use"); + } + + String contentType = messageFormatter.getContentType( + msgContext, format, msgContext.getSoapAction()); + + boolean useBytesMessage = + msgType != null && JMSConstants.JMS_BYTE_MESSAGE.equals(msgType) || + contentType.indexOf(HTTPConstants.HEADER_ACCEPT_MULTIPART_RELATED) > -1; + + OutputStream out; + StringWriter sw; + if (useBytesMessage) { + BytesMessage bytesMsg = session.createBytesMessage(); + sw = null; + out = new BytesMessageOutputStream(bytesMsg); + message = bytesMsg; + } else { + sw = new StringWriter(); + try { + out = new WriterOutputStream(sw, format.getCharSetEncoding()); + } catch (UnsupportedCharsetException ex) { + handleException("Unsupported encoding " + format.getCharSetEncoding(), ex); + return null; + } + } + + try { + messageFormatter.writeTo(msgContext, format, out, true); + out.close(); + } catch (IOException e) { + handleException("IO Error while creating BytesMessage", e); + } + + if (!useBytesMessage) { + TextMessage txtMsg = session.createTextMessage(); + txtMsg.setText(sw.toString()); + message = txtMsg; + } + + if (contentTypeProperty != null) { + message.setStringProperty(contentTypeProperty, contentType); + } + + } else if (JMSConstants.JMS_BYTE_MESSAGE.equals(jmsPayloadType)) { + message = session.createBytesMessage(); + BytesMessage bytesMsg = (BytesMessage) message; + OMElement wrapper = msgContext.getEnvelope().getBody(). + getFirstChildWithName(BaseConstants.DEFAULT_BINARY_WRAPPER); + OMNode omNode = wrapper.getFirstOMChild(); + if (omNode != null && omNode instanceof OMText) { + Object dh = ((OMText) omNode).getDataHandler(); + if (dh != null && dh instanceof DataHandler) { + try { + ((DataHandler) dh).writeTo(new BytesMessageOutputStream(bytesMsg)); + } catch (IOException e) { + handleException("Error serializing binary content of element : " + + BaseConstants.DEFAULT_BINARY_WRAPPER, e); + } + } + } + + } else if (JMSConstants.JMS_TEXT_MESSAGE.equals(jmsPayloadType)) { + message = session.createTextMessage(); + TextMessage txtMsg = (TextMessage) message; + txtMsg.setText(msgContext.getEnvelope().getBody(). + getFirstChildWithName(BaseConstants.DEFAULT_TEXT_WRAPPER).getText()); + } + + // set the JMS correlation ID if specified + String correlationId = getProperty(msgContext, JMSConstants.JMS_COORELATION_ID); + if (correlationId == null && msgContext.getRelatesTo() != null) { + correlationId = msgContext.getRelatesTo().getValue(); + } + + if (correlationId != null) { + message.setJMSCorrelationID(correlationId); + } + + if (msgContext.isServerSide()) { + // set SOAP Action as a property on the JMS message + setProperty(message, msgContext, BaseConstants.SOAPACTION); + } else { + String action = msgContext.getOptions().getAction(); + if (action != null) { + message.setStringProperty(BaseConstants.SOAPACTION, action); + } + } + + JMSUtils.setTransportHeaders(msgContext, message); + return message; + } + + /** + * Guess the message type to use for JMS looking at the message contexts' envelope + * @param msgContext the message context + * @return JMSConstants.JMS_BYTE_MESSAGE or JMSConstants.JMS_TEXT_MESSAGE or null + */ + private String guessMessageType(MessageContext msgContext) { + OMElement firstChild = msgContext.getEnvelope().getBody().getFirstElement(); + if (firstChild != null) { + if (BaseConstants.DEFAULT_BINARY_WRAPPER.equals(firstChild.getQName())) { + return JMSConstants.JMS_BYTE_MESSAGE; + } else if (BaseConstants.DEFAULT_TEXT_WRAPPER.equals(firstChild.getQName())) { + return JMSConstants.JMS_TEXT_MESSAGE; + } + } + return null; + } + + /** + * Creates an Axis MessageContext for the received JMS message and + * sets up the transports and various properties + * + * @param outMsgCtx the outgoing message for which we are expecting the response + * @param message the JMS response message received + * @param contentTypeProperty the message property used to determine the content type + * of the response message + * @throws AxisFault on error + */ + private void processSyncResponse(MessageContext outMsgCtx, Message message, + String contentTypeProperty) throws AxisFault { + + MessageContext responseMsgCtx = createResponseMessageContext(outMsgCtx); + + // load any transport headers from received message + JMSUtils.loadTransportHeaders(message, responseMsgCtx); + + // workaround for Axis2 TransportUtils.createSOAPMessage() issue, where a response + // of content type "text/xml" is thought to be REST if !MC.isServerSide(). This + // question is still under debate and due to the timelines, I am commiting this + // workaround as Axis2 1.2 is about to be released and Synapse 1.0 + responseMsgCtx.setServerSide(false); + + String contentType = + contentTypeProperty == null ? null + : JMSUtils.getProperty(message, contentTypeProperty); + + try { + JMSUtils.setSOAPEnvelope(message, responseMsgCtx, contentType); + } catch (JMSException ex) { + throw AxisFault.makeFault(ex); + } +// responseMsgCtx.setServerSide(true); + + handleIncomingMessage( + responseMsgCtx, + JMSUtils.getTransportHeaders(message), + JMSUtils.getProperty(message, BaseConstants.SOAPACTION), + contentType + ); + } + + private void setProperty(Message message, MessageContext msgCtx, String key) { + + String value = getProperty(msgCtx, key); + if (value != null) { + try { + message.setStringProperty(key, value); + } catch (JMSException e) { + log.warn("Couldn't set message property : " + key + " = " + value, e); + } + } + } + + private String getProperty(MessageContext mc, String key) { + return (String) mc.getProperty(key); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSUtils.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSUtils.java new file mode 100644 index 0000000000..63faa0b852 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/JMSUtils.java @@ -0,0 +1,1115 @@ +/* +* Copyright 2004,2005 The Apache Software Foundation. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.jms; + +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; + +import javax.jms.BytesMessage; +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.QueueConnection; +import javax.jms.QueueConnectionFactory; +import javax.jms.QueueSession; +import javax.jms.Session; +import javax.jms.TextMessage; +import javax.jms.Topic; +import javax.jms.TopicConnection; +import javax.jms.TopicConnectionFactory; +import javax.jms.TopicSession; +import javax.mail.internet.ContentType; +import javax.mail.internet.ParseException; +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.Reference; + +import org.apache.axiom.om.OMElement; +import org.apache.axis2.AxisFault; +import org.apache.axis2.Constants; +import org.apache.axis2.builder.Builder; +import org.apache.axis2.builder.BuilderUtil; +import org.apache.axis2.builder.SOAPBuilder; +import org.apache.axis2.context.MessageContext; +import org.apache.axis2.description.AxisService; +import org.apache.axis2.description.Parameter; +import org.apache.axis2.transport.TransportUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.tuscany.sca.binding.ws.axis2.format.DataSourceMessageBuilder; +import org.apache.tuscany.sca.binding.ws.axis2.format.TextMessageBuilder; +import org.apache.tuscany.sca.binding.ws.axis2.format.TextMessageBuilderAdapter; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.BaseConstants; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.BaseUtils; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.threads.WorkerPool; + +/** + * Miscallaneous methods used for the JMS transport + */ +public class JMSUtils extends BaseUtils { + + private static final Log log = LogFactory.getLog(JMSUtils.class); + private static final Class[] NOARGS = new Class[] {}; + private static final Object[] NOPARMS = new Object[] {}; + + /** + * Should this service be enabled over the JMS transport? + * + * @param service the Axis service + * @return true if JMS should be enabled + */ + public static boolean isJMSService(AxisService service) { + if (service.isEnableAllTransports()) { + return true; + + } else { + List transports = service.getExposedTransports(); + for (Object transport : transports) { + if (JMSListener.TRANSPORT_NAME.equals(transport)) { + return true; + } + } + } + return false; + } + + /** + * Get the EPR for the given JMS connection factory and destination + * the form of the URL is + * jms:/<destination>?[<key>=<value>&]* + * Credentials Context.SECURITY_PRINCIPAL, Context.SECURITY_CREDENTIALS + * JMSConstants.PARAM_JMS_USERNAME and JMSConstants.PARAM_JMS_USERNAME are filtered + * + * @param cf the Axis2 JMS connection factory + * @param destinationType the type of destination + * @param endpoint JMSEndpoint + * @return the EPR as a String + */ + static String getEPR(JMSConnectionFactory cf, int destinationType, JMSEndpoint endpoint) { + StringBuffer sb = new StringBuffer(); + + sb.append( + JMSConstants.JMS_PREFIX).append(endpoint.getJndiDestinationName()); + sb.append("?"). + append(JMSConstants.PARAM_DEST_TYPE).append("=").append( + destinationType == JMSConstants.TOPIC ? + JMSConstants.DESTINATION_TYPE_TOPIC : JMSConstants.DESTINATION_TYPE_QUEUE); + + if (endpoint.getContentTypeRuleSet() != null) { + String contentTypeProperty = + endpoint.getContentTypeRuleSet().getDefaultContentTypeProperty(); + if (contentTypeProperty != null) { + sb.append("&"); + sb.append(JMSConstants.CONTENT_TYPE_PROPERTY_PARAM); + sb.append("="); + sb.append(contentTypeProperty); + } + } + + for (Map.Entry<String,String> entry : cf.getParameters().entrySet()) { + if (!Context.SECURITY_PRINCIPAL.equalsIgnoreCase(entry.getKey()) && + !Context.SECURITY_CREDENTIALS.equalsIgnoreCase(entry.getKey()) && + !JMSConstants.PARAM_JMS_USERNAME.equalsIgnoreCase(entry.getKey()) && + !JMSConstants.PARAM_JMS_PASSWORD.equalsIgnoreCase(entry.getKey())) { + sb.append("&").append( + entry.getKey()).append("=").append(entry.getValue()); + } + } + return sb.toString(); + } + + /** + * Get a String property from the JMS message + * + * @param message JMS message + * @param property property name + * @return property value + */ + public static String getProperty(Message message, String property) { + try { + return message.getStringProperty(property); + } catch (JMSException e) { + return null; + } + } + + /** + * Return the destination name from the given URL + * + * @param url the URL + * @return the destination name + */ + public static String getDestination(String url) { + String tempUrl = url.substring(JMSConstants.JMS_PREFIX.length()); + int propPos = tempUrl.indexOf("?"); + + if (propPos == -1) { + return tempUrl; + } else { + return tempUrl.substring(0, propPos); + } + } + + /** + * Set the SOAPEnvelope to the Axis2 MessageContext, from the JMS Message passed in + * @param message the JMS message read + * @param msgContext the Axis2 MessageContext to be populated + * @param contentType content type for the message + * @throws AxisFault + * @throws JMSException + */ + public static void setSOAPEnvelope(Message message, MessageContext msgContext, String contentType) + throws AxisFault, JMSException { + + if (contentType == null) { + if (message instanceof TextMessage) { + contentType = "text/plain"; + } else { + contentType = "application/octet-stream"; + } + if (log.isDebugEnabled()) { + log.debug("No content type specified; assuming " + contentType); + } + } + + int index = contentType.indexOf(';'); + String type = index > 0 ? contentType.substring(0, index) : contentType; + Builder builder = BuilderUtil.getBuilderFromSelector(type, msgContext); + if (builder == null) { + if (log.isDebugEnabled()) { + log.debug("No message builder found for type '" + type + "'. Falling back to SOAP."); + } + builder = new SOAPBuilder(); + } + + OMElement documentElement; + if (message instanceof BytesMessage) { + // Extract the charset encoding from the content type and + // set the CHARACTER_SET_ENCODING property as e.g. SOAPBuilder relies on this. + String charSetEnc = null; + try { + if (contentType != null) { + charSetEnc = new ContentType(contentType).getParameter("charset"); + } + } catch (ParseException ex) { + // ignore + } + msgContext.setProperty(Constants.Configuration.CHARACTER_SET_ENCODING, charSetEnc); + + if (builder instanceof DataSourceMessageBuilder) { + documentElement = ((DataSourceMessageBuilder)builder).processDocument( + new BytesMessageDataSource((BytesMessage)message), contentType, + msgContext); + } else { + documentElement = builder.processDocument( + new BytesMessageInputStream((BytesMessage)message), contentType, + msgContext); + } + } else if (message instanceof TextMessage) { + TextMessageBuilder textMessageBuilder; + if (builder instanceof TextMessageBuilder) { + textMessageBuilder = (TextMessageBuilder)builder; + } else { + textMessageBuilder = new TextMessageBuilderAdapter(builder); + } + String content = ((TextMessage)message).getText(); + documentElement = textMessageBuilder.processDocument(content, contentType, msgContext); + } else { + handleException("Unsupported JMS message type " + message.getClass().getName()); + return; // Make compiler happy + } + msgContext.setEnvelope(TransportUtils.createSOAPEnvelope(documentElement)); + } + + /** + * Set the JMS ReplyTo for the message + * + * @param replyDestination the JMS Destination where the reply is expected + * @param session the session to use to create a temp Queue if a response is expected + * but a Destination has not been specified + * @param message the JMS message where the final Destinatio would be set as the JMS ReplyTo + * @return the JMS ReplyTo Destination for the message + */ + public static Destination setReplyDestination(Destination replyDestination, Session session, + Message message) { + + if (replyDestination == null) { + try { + // create temporary queue to receive the reply + replyDestination = createTemporaryDestination(session); + } catch (JMSException e) { + handleException("Error creating temporary queue for response"); + } + } + + try { + message.setJMSReplyTo(replyDestination); + } catch (JMSException e) { + log.warn("Error setting JMS ReplyTo destination to : " + replyDestination, e); + } + + if (log.isDebugEnabled()) { + try { + assert replyDestination != null; + log.debug("Expecting a response to JMS Destination : " + + (replyDestination instanceof Queue ? + ((Queue) replyDestination).getQueueName() : + ((Topic) replyDestination).getTopicName())); + } catch (JMSException ignore) {} + } + return replyDestination; + } + + /** + * Set transport headers from the axis message context, into the JMS message + * + * @param msgContext the axis message context + * @param message the JMS Message + * @throws JMSException on exception + */ + public static void setTransportHeaders(MessageContext msgContext, Message message) + throws JMSException { + + Map headerMap = (Map) msgContext.getProperty(MessageContext.TRANSPORT_HEADERS); + + if (headerMap == null) { + return; + } + + for (Object headerName : headerMap.keySet()) { + + String name = (String) headerName; + + if (name.startsWith(JMSConstants.JMSX_PREFIX) && + !(name.equals(JMSConstants.JMSX_GROUP_ID) || name.equals(JMSConstants.JMSX_GROUP_SEQ))) { + continue; + } + + if (JMSConstants.JMS_COORELATION_ID.equals(name)) { + message.setJMSCorrelationID( + (String) headerMap.get(JMSConstants.JMS_COORELATION_ID)); + } else if (JMSConstants.JMS_DELIVERY_MODE.equals(name)) { + Object o = headerMap.get(JMSConstants.JMS_DELIVERY_MODE); + if (o instanceof Integer) { + message.setJMSDeliveryMode((Integer) o); + } else if (o instanceof String) { + try { + message.setJMSDeliveryMode(Integer.parseInt((String) o)); + } catch (NumberFormatException nfe) { + log.warn("Invalid delivery mode ignored : " + o, nfe); + } + } else { + log.warn("Invalid delivery mode ignored : " + o); + } + + } else if (JMSConstants.JMS_EXPIRATION.equals(name)) { + message.setJMSExpiration( + Long.parseLong((String) headerMap.get(JMSConstants.JMS_EXPIRATION))); + } else if (JMSConstants.JMS_MESSAGE_ID.equals(name)) { + message.setJMSMessageID((String) headerMap.get(JMSConstants.JMS_MESSAGE_ID)); + } else if (JMSConstants.JMS_PRIORITY.equals(name)) { + message.setJMSPriority( + Integer.parseInt((String) headerMap.get(JMSConstants.JMS_PRIORITY))); + } else if (JMSConstants.JMS_TIMESTAMP.equals(name)) { + message.setJMSTimestamp( + Long.parseLong((String) headerMap.get(JMSConstants.JMS_TIMESTAMP))); + } else if (JMSConstants.JMS_MESSAGE_TYPE.equals(name)) { + message.setJMSType((String) headerMap.get(JMSConstants.JMS_MESSAGE_TYPE)); + + } else { + Object value = headerMap.get(name); + if (value instanceof String) { + message.setStringProperty(name, (String) value); + } else if (value instanceof Boolean) { + message.setBooleanProperty(name, (Boolean) value); + } else if (value instanceof Integer) { + message.setIntProperty(name, (Integer) value); + } else if (value instanceof Long) { + message.setLongProperty(name, (Long) value); + } else if (value instanceof Double) { + message.setDoubleProperty(name, (Double) value); + } else if (value instanceof Float) { + message.setFloatProperty(name, (Float) value); + } + } + } + } + + /** + * Read the transport headers from the JMS Message and set them to the axis2 message context + * + * @param message the JMS Message received + * @param responseMsgCtx the axis message context + * @throws AxisFault on error + */ + public static void loadTransportHeaders(Message message, MessageContext responseMsgCtx) + throws AxisFault { + responseMsgCtx.setProperty(MessageContext.TRANSPORT_HEADERS, getTransportHeaders(message)); + } + + /** + * Extract transport level headers for JMS from the given message into a Map + * + * @param message the JMS message + * @return a Map of the transport headers + */ + public static Map<String, Object> getTransportHeaders(Message message) { + // create a Map to hold transport headers + Map<String, Object> map = new HashMap<String, Object>(); + + // correlation ID + try { + if (message.getJMSCorrelationID() != null) { + map.put(JMSConstants.JMS_COORELATION_ID, message.getJMSCorrelationID()); + } + } catch (JMSException ignore) {} + + // set the delivery mode as persistent or not + try { + map.put(JMSConstants.JMS_DELIVERY_MODE, Integer.toString(message.getJMSDeliveryMode())); + } catch (JMSException ignore) {} + + // destination name + try { + if (message.getJMSDestination() != null) { + Destination dest = message.getJMSDestination(); + map.put(JMSConstants.JMS_DESTINATION, + dest instanceof Queue ? + ((Queue) dest).getQueueName() : ((Topic) dest).getTopicName()); + } + } catch (JMSException ignore) {} + + // expiration + try { + map.put(JMSConstants.JMS_EXPIRATION, Long.toString(message.getJMSExpiration())); + } catch (JMSException ignore) {} + + // if a JMS message ID is found + try { + if (message.getJMSMessageID() != null) { + map.put(JMSConstants.JMS_MESSAGE_ID, message.getJMSMessageID()); + } + } catch (JMSException ignore) {} + + // priority + try { + map.put(JMSConstants.JMS_PRIORITY, Long.toString(message.getJMSPriority())); + } catch (JMSException ignore) {} + + // redelivered + try { + map.put(JMSConstants.JMS_REDELIVERED, Boolean.toString(message.getJMSRedelivered())); + } catch (JMSException ignore) {} + + // replyto destination name + try { + if (message.getJMSReplyTo() != null) { + Destination dest = message.getJMSReplyTo(); + map.put(JMSConstants.JMS_REPLY_TO, + dest instanceof Queue ? + ((Queue) dest).getQueueName() : ((Topic) dest).getTopicName()); + } + } catch (JMSException ignore) {} + + // priority + try { + map.put(JMSConstants.JMS_TIMESTAMP, Long.toString(message.getJMSTimestamp())); + } catch (JMSException ignore) {} + + // message type + try { + if (message.getJMSType() != null) { + map.put(JMSConstants.JMS_TYPE, message.getJMSType()); + } + } catch (JMSException ignore) {} + + // any other transport properties / headers + Enumeration e = null; + try { + e = message.getPropertyNames(); + } catch (JMSException ignore) {} + + if (e != null) { + while (e.hasMoreElements()) { + String headerName = (String) e.nextElement(); + try { + map.put(headerName, message.getStringProperty(headerName)); + continue; + } catch (JMSException ignore) {} + try { + map.put(headerName, message.getBooleanProperty(headerName)); + continue; + } catch (JMSException ignore) {} + try { + map.put(headerName, message.getIntProperty(headerName)); + continue; + } catch (JMSException ignore) {} + try { + map.put(headerName, message.getLongProperty(headerName)); + continue; + } catch (JMSException ignore) {} + try { + map.put(headerName, message.getDoubleProperty(headerName)); + continue; + } catch (JMSException ignore) {} + try { + map.put(headerName, message.getFloatProperty(headerName)); + } catch (JMSException ignore) {} + } + } + + return map; + } + + + /** + * Create a MessageConsumer for the given Destination + * @param session JMS Session to use + * @param dest Destination for which the Consumer is to be created + * @param messageSelector the message selector to be used if any + * @return a MessageConsumer for the specified Destination + * @throws JMSException + */ + public static MessageConsumer createConsumer(Session session, Destination dest, String messageSelector) + throws JMSException { + + if (dest instanceof Queue) { + return ((QueueSession) session).createReceiver((Queue) dest, messageSelector); + } else { + return ((TopicSession) session).createSubscriber((Topic) dest, messageSelector, false); + } + } + + /** + * Create a temp queue or topic for synchronous receipt of responses, when a reply destination + * is not specified + * @param session the JMS Session to use + * @return a temporary Queue or Topic, depending on the session + * @throws JMSException + */ + public static Destination createTemporaryDestination(Session session) throws JMSException { + + if (session instanceof QueueSession) { + return session.createTemporaryQueue(); + } else { + return session.createTemporaryTopic(); + } + } + + /** + * Return the body length in bytes for a bytes message + * @param bMsg the JMS BytesMessage + * @return length of body in bytes + */ + public static long getBodyLength(BytesMessage bMsg) { + try { + Method mtd = bMsg.getClass().getMethod("getBodyLength", NOARGS); + if (mtd != null) { + return (Long) mtd.invoke(bMsg, NOPARMS); + } + } catch (Exception e) { + // JMS 1.0 + if (log.isDebugEnabled()) { + log.debug("Error trying to determine JMS BytesMessage body length", e); + } + } + + // if JMS 1.0 + long length = 0; + try { + byte[] buffer = new byte[2048]; + bMsg.reset(); + for (int bytesRead = bMsg.readBytes(buffer); bytesRead != -1; + bytesRead = bMsg.readBytes(buffer)) { + length += bytesRead; + } + } catch (JMSException ignore) {} + return length; + } + + /** + * Get the length of the message in bytes + * @param message + * @return message size (or approximation) in bytes + * @throws JMSException + */ + public static long getMessageSize(Message message) throws JMSException { + if (message instanceof BytesMessage) { + return JMSUtils.getBodyLength((BytesMessage) message); + } else if (message instanceof TextMessage) { + // TODO: Converting the whole message to a byte array is too much overhead just to determine the message size. + // Anyway, the result is not accurate since we don't know what encoding the JMS provider uses. + return ((TextMessage) message).getText().getBytes().length; + } else { + log.warn("Can't determine size of JMS message; unsupported message type : " + + message.getClass().getName()); + return 0; + } + } + + public static <T> T lookup(Context context, Class<T> clazz, String name) + throws NamingException { + + Object object = context.lookup(name); + try { + return clazz.cast(object); + } catch (ClassCastException ex) { + // Instead of a ClassCastException, throw an exception with some + // more information. + if (object instanceof Reference) { + Reference ref = (Reference)object; + handleException("JNDI failed to de-reference Reference with name " + + name + "; is the factory " + ref.getFactoryClassName() + + " in your classpath?"); + return null; + } else { + handleException("JNDI lookup of name " + name + " returned a " + + object.getClass().getName() + " while a " + clazz + " was expected"); + return null; + } + } + } + + /** + * Create a ServiceTaskManager for the service passed in and its corresponding JMSConnectionFactory + * @param jcf + * @param service + * @param workerPool + * @return + */ + public static ServiceTaskManager createTaskManagerForService(JMSConnectionFactory jcf, + AxisService service, WorkerPool workerPool) { + + String name = service.getName(); + Map<String, String> svc = getServiceStringParameters(service.getParameters()); + Map<String, String> cf = jcf.getParameters(); + + ServiceTaskManager stm = new ServiceTaskManager(); + + stm.setServiceName(name); + stm.addJmsProperties(cf); + stm.addJmsProperties(svc); + + stm.setConnFactoryJNDIName( + getRqdStringProperty(JMSConstants.PARAM_CONFAC_JNDI_NAME, svc, cf)); + String destName = getOptionalStringProperty(JMSConstants.PARAM_DESTINATION, svc, cf); + if (destName == null) { + destName = service.getName(); + } + stm.setDestinationJNDIName(destName); + stm.setDestinationType(getDestinationType(svc, cf)); + + stm.setJmsSpec11( + getJMSSpecVersion(svc, cf)); + stm.setTransactionality( + getTransactionality(svc, cf)); + stm.setCacheUserTransaction( + getOptionalBooleanProperty(BaseConstants.PARAM_CACHE_USER_TXN, svc, cf)); + stm.setUserTransactionJNDIName( + getOptionalStringProperty(BaseConstants.PARAM_USER_TXN_JNDI_NAME, svc, cf)); + stm.setSessionTransacted( + getOptionalBooleanProperty(JMSConstants.PARAM_SESSION_TRANSACTED, svc, cf)); + stm.setSessionAckMode( + getSessionAck(svc, cf)); + stm.setMessageSelector( + getOptionalStringProperty(JMSConstants.PARAM_MSG_SELECTOR, svc, cf)); + stm.setSubscriptionDurable( + getOptionalBooleanProperty(JMSConstants.PARAM_SUB_DURABLE, svc, cf)); + stm.setDurableSubscriberName( + getOptionalStringProperty(JMSConstants.PARAM_DURABLE_SUB_NAME, svc, cf)); + + stm.setCacheLevel( + getCacheLevel(svc, cf)); + stm.setPubSubNoLocal( + getOptionalBooleanProperty(JMSConstants.PARAM_PUBSUB_NO_LOCAL, svc, cf)); + + Integer value = getOptionalIntProperty(JMSConstants.PARAM_RCV_TIMEOUT, svc, cf); + if (value != null) { + stm.setReceiveTimeout(value); + } + value = getOptionalIntProperty(JMSConstants.PARAM_CONCURRENT_CONSUMERS, svc, cf); + if (value != null) { + stm.setConcurrentConsumers(value); + } + value = getOptionalIntProperty(JMSConstants.PARAM_MAX_CONSUMERS, svc, cf); + if (value != null) { + stm.setMaxConcurrentConsumers(value); + } + value = getOptionalIntProperty(JMSConstants.PARAM_IDLE_TASK_LIMIT, svc, cf); + if (value != null) { + stm.setIdleTaskExecutionLimit(value); + } + value = getOptionalIntProperty(JMSConstants.PARAM_MAX_MSGS_PER_TASK, svc, cf); + if (value != null) { + stm.setMaxMessagesPerTask(value); + } + + value = getOptionalIntProperty(JMSConstants.PARAM_RECON_INIT_DURATION, svc, cf); + if (value != null) { + stm.setInitialReconnectDuration(value); + } + value = getOptionalIntProperty(JMSConstants.PARAM_RECON_MAX_DURATION, svc, cf); + if (value != null) { + stm.setMaxReconnectDuration(value); + } + Double dValue = getOptionalDoubleProperty(JMSConstants.PARAM_RECON_FACTOR, svc, cf); + if (dValue != null) { + stm.setReconnectionProgressionFactor(dValue); + } + + stm.setWorkerPool(workerPool); + + // remove processed properties from property bag + stm.removeJmsProperties(JMSConstants.PARAM_CONFAC_JNDI_NAME); + stm.removeJmsProperties(JMSConstants.PARAM_DESTINATION); + stm.removeJmsProperties(JMSConstants.PARAM_JMS_SPEC_VER); + stm.removeJmsProperties(BaseConstants.PARAM_TRANSACTIONALITY); + stm.removeJmsProperties(BaseConstants.PARAM_CACHE_USER_TXN); + stm.removeJmsProperties(BaseConstants.PARAM_USER_TXN_JNDI_NAME); + stm.removeJmsProperties(JMSConstants.PARAM_SESSION_TRANSACTED); + stm.removeJmsProperties(JMSConstants.PARAM_MSG_SELECTOR); + stm.removeJmsProperties(JMSConstants.PARAM_SUB_DURABLE); + stm.removeJmsProperties(JMSConstants.PARAM_DURABLE_SUB_NAME); + stm.removeJmsProperties(JMSConstants.PARAM_CACHE_LEVEL); + stm.removeJmsProperties(JMSConstants.PARAM_PUBSUB_NO_LOCAL); + stm.removeJmsProperties(JMSConstants.PARAM_RCV_TIMEOUT); + stm.removeJmsProperties(JMSConstants.PARAM_CONCURRENT_CONSUMERS); + stm.removeJmsProperties(JMSConstants.PARAM_MAX_CONSUMERS); + stm.removeJmsProperties(JMSConstants.PARAM_IDLE_TASK_LIMIT); + stm.removeJmsProperties(JMSConstants.PARAM_MAX_MSGS_PER_TASK); + stm.removeJmsProperties(JMSConstants.PARAM_RECON_INIT_DURATION); + stm.removeJmsProperties(JMSConstants.PARAM_RECON_MAX_DURATION); + stm.removeJmsProperties(JMSConstants.PARAM_RECON_FACTOR); + + return stm; + } + + private static Map<String, String> getServiceStringParameters(List list) { + + Map<String, String> map = new HashMap<String, String>(); + for (Object o : list) { + Parameter p = (Parameter) o; + if (p.getValue() instanceof String) { + map.put(p.getName(), (String) p.getValue()); + } + } + return map; + } + + private static String getRqdStringProperty(String key, Map svcMap, Map cfMap) { + String value = (String) svcMap.get(key); + if (value == null) { + value = (String) cfMap.get(key); + } + if (value == null) { + throw new AxisJMSException("Service/connection factory property : " + key); + } + return value; + } + + private static String getOptionalStringProperty(String key, Map svcMap, Map cfMap) { + String value = (String) svcMap.get(key); + if (value == null) { + value = (String) cfMap.get(key); + } + return value; + } + + private static Boolean getOptionalBooleanProperty(String key, Map svcMap, Map cfMap) { + String value = (String) svcMap.get(key); + if (value == null) { + value = (String) cfMap.get(key); + } + if (value == null) { + return null; + } else { + return Boolean.valueOf(value); + } + } + + private static Integer getOptionalIntProperty(String key, Map svcMap, Map cfMap) { + String value = (String) svcMap.get(key); + if (value == null) { + value = (String) cfMap.get(key); + } + if (value != null) { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new AxisJMSException("Invalid value : " + value + " for " + key); + } + } + return null; + } + + private static Double getOptionalDoubleProperty(String key, Map svcMap, Map cfMap) { + String value = (String) svcMap.get(key); + if (value == null) { + value = (String) cfMap.get(key); + } + if (value != null) { + try { + return Double.parseDouble(value); + } catch (NumberFormatException e) { + throw new AxisJMSException("Invalid value : " + value + " for " + key); + } + } + return null; + } + + private static int getTransactionality(Map svcMap, Map cfMap) { + + String key = BaseConstants.PARAM_TRANSACTIONALITY; + String val = (String) svcMap.get(key); + if (val == null) { + val = (String) cfMap.get(key); + } + + if (val == null) { + return BaseConstants.TRANSACTION_NONE; + + } else { + if (BaseConstants.STR_TRANSACTION_JTA.equalsIgnoreCase(val)) { + return BaseConstants.TRANSACTION_JTA; + } else if (BaseConstants.STR_TRANSACTION_LOCAL.equalsIgnoreCase(val)) { + return BaseConstants.TRANSACTION_LOCAL; + } else { + throw new AxisJMSException("Invalid option : " + val + " for parameter : " + + BaseConstants.STR_TRANSACTION_JTA); + } + } + } + + private static int getDestinationType(Map svcMap, Map cfMap) { + + String key = JMSConstants.PARAM_DEST_TYPE; + String val = (String) svcMap.get(key); + if (val == null) { + val = (String) cfMap.get(key); + } + + if (JMSConstants.DESTINATION_TYPE_TOPIC.equalsIgnoreCase(val)) { + return JMSConstants.TOPIC; + } + return JMSConstants.QUEUE; + } + + private static int getSessionAck(Map svcMap, Map cfMap) { + + String key = JMSConstants.PARAM_SESSION_ACK; + String val = (String) svcMap.get(key); + if (val == null) { + val = (String) cfMap.get(key); + } + + if (val == null || "AUTO_ACKNOWLEDGE".equalsIgnoreCase(val)) { + return Session.AUTO_ACKNOWLEDGE; + } else if ("CLIENT_ACKNOWLEDGE".equalsIgnoreCase(val)) { + return Session.CLIENT_ACKNOWLEDGE; + } else if ("DUPS_OK_ACKNOWLEDGE".equals(val)){ + return Session.DUPS_OK_ACKNOWLEDGE; + } else if ("SESSION_TRANSACTED".equals(val)) { + return 0; //Session.SESSION_TRANSACTED; + } else { + try { + return Integer.parseInt(val); + } catch (NumberFormatException ignore) { + throw new AxisJMSException("Invalid session acknowledgement mode : " + val); + } + } + } + + private static int getCacheLevel(Map svcMap, Map cfMap) { + + String key = JMSConstants.PARAM_CACHE_LEVEL; + String val = (String) svcMap.get(key); + if (val == null) { + val = (String) cfMap.get(key); + } + + if ("none".equalsIgnoreCase(val)) { + return JMSConstants.CACHE_NONE; + } else if ("connection".equalsIgnoreCase(val)) { + return JMSConstants.CACHE_CONNECTION; + } else if ("session".equals(val)){ + return JMSConstants.CACHE_SESSION; + } else if ("consumer".equals(val)) { + return JMSConstants.CACHE_CONSUMER; + } else if (val != null) { + throw new AxisJMSException("Invalid cache level : " + val); + } + return JMSConstants.CACHE_AUTO; + } + + private static boolean getJMSSpecVersion(Map svcMap, Map cfMap) { + + String key = JMSConstants.PARAM_JMS_SPEC_VER; + String val = (String) svcMap.get(key); + if (val == null) { + val = (String) cfMap.get(key); + } + + if (val == null || "1.1".equals(val)) { + return true; + } else { + return false; + } + } + + /** + * This is a JMS spec independent method to create a Connection. Please be cautious when + * making any changes + * + * @param conFac the ConnectionFactory to use + * @param user optional user name + * @param pass optional password + * @param jmsSpec11 should we use JMS 1.1 API ? + * @param isQueue is this to deal with a Queue? + * @return a JMS Connection as requested + * @throws JMSException on errors, to be handled and logged by the caller + */ + public static Connection createConnection(ConnectionFactory conFac, + String user, String pass, boolean jmsSpec11, Boolean isQueue) throws JMSException { + + Connection connection = null; + if (log.isDebugEnabled()) { + log.debug("Creating a " + (isQueue == null ? "Generic" : isQueue ? "Queue" : "Topic") + + "Connection using credentials : (" + user + "/" + pass + ")"); + } + + if (jmsSpec11 || isQueue == null) { + if (user != null && pass != null) { + connection = conFac.createConnection(user, pass); + } else { + connection = conFac.createConnection(); + } + + } else { + QueueConnectionFactory qConFac = null; + TopicConnectionFactory tConFac = null; + if (isQueue) { + tConFac = (TopicConnectionFactory) conFac; + } else { + qConFac = (QueueConnectionFactory) conFac; + } + + if (user != null && pass != null) { + if (qConFac != null) { + connection = qConFac.createQueueConnection(user, pass); + } else if (tConFac != null) { + connection = tConFac.createTopicConnection(user, pass); + } + } else { + if (qConFac != null) { + connection = qConFac.createQueueConnection(); + } else if (tConFac != null) { + connection = tConFac.createTopicConnection(); + } + } + } + return connection; + } + + /** + * This is a JMS spec independent method to create a Session. Please be cautious when + * making any changes + * + * @param connection the JMS Connection + * @param transacted should the session be transacted? + * @param ackMode the ACK mode for the session + * @param jmsSpec11 should we use the JMS 1.1 API? + * @param isQueue is this Session to deal with a Queue? + * @return a Session created for the given information + * @throws JMSException on errors, to be handled and logged by the caller + */ + public static Session createSession(Connection connection, boolean transacted, int ackMode, + boolean jmsSpec11, Boolean isQueue) throws JMSException { + + if (jmsSpec11 || isQueue == null) { + return connection.createSession(transacted, ackMode); + + } else { + if (isQueue) { + return ((QueueConnection) connection).createQueueSession(transacted, ackMode); + } else { + return ((TopicConnection) connection).createTopicSession(transacted, ackMode); + } + } + } + + /** + * This is a JMS spec independent method to create a MessageConsumer. Please be cautious when + * making any changes + * + * @param session JMS session + * @param destination the Destination + * @param isQueue is the Destination a queue? + * @param subscriberName optional client name to use for a durable subscription to a topic + * @param messageSelector optional message selector + * @param pubSubNoLocal should we receive messages sent by us during pub-sub? + * @param isDurable is this a durable topic subscription? + * @param jmsSpec11 should we use JMS 1.1 API ? + * @return a MessageConsumer to receive messages + * @throws JMSException on errors, to be handled and logged by the caller + */ + public static MessageConsumer createConsumer( + Session session, Destination destination, Boolean isQueue, + String subscriberName, String messageSelector, boolean pubSubNoLocal, + boolean isDurable, boolean jmsSpec11) throws JMSException { + + if (jmsSpec11 || isQueue == null) { + if (isDurable) { + return session.createDurableSubscriber( + (Topic) destination, subscriberName, messageSelector, pubSubNoLocal); + } else { + return session.createConsumer(destination, messageSelector, pubSubNoLocal); + } + } else { + if (isQueue) { + return ((QueueSession) session).createReceiver((Queue) destination, messageSelector); + } else { + if (isDurable) { + return ((TopicSession) session).createDurableSubscriber( + (Topic) destination, subscriberName, messageSelector, pubSubNoLocal); + } else { + return ((TopicSession) session).createSubscriber( + (Topic) destination, messageSelector, pubSubNoLocal); + } + } + } + } + + /** + * This is a JMS spec independent method to create a MessageProducer. Please be cautious when + * making any changes + * + * @param session JMS session + * @param destination the Destination + * @param isQueue is the Destination a queue? + * @param jmsSpec11 should we use JMS 1.1 API ? + * @return a MessageProducer to send messages to the given Destination + * @throws JMSException on errors, to be handled and logged by the caller + */ + public static MessageProducer createProducer( + Session session, Destination destination, Boolean isQueue, boolean jmsSpec11) throws JMSException { + + if (jmsSpec11 || isQueue == null) { + return session.createProducer(destination); + } else { + if (isQueue) { + return ((QueueSession) session).createSender((Queue) destination); + } else { + return ((TopicSession) session).createPublisher((Topic) destination); + } + } + } + + /** + * Create a one time MessageProducer for the given JMS OutTransport information + * For simplicity and best compatibility, this method uses only JMS 1.0.2b API. + * Please be cautious when making any changes + * + * @param jmsOut the JMS OutTransport information (contains all properties) + * @return a JMSSender based on one-time use resources + * @throws JMSException on errors, to be handled and logged by the caller + */ + public static JMSMessageSender createJMSSender(JMSOutTransportInfo jmsOut) + throws JMSException { + + // digest the targetAddress and locate CF from the EPR + jmsOut.loadConnectionFactoryFromProperies(); + + // create a one time connection and session to be used + Hashtable<String,String> jmsProps = jmsOut.getProperties(); + String user = jmsProps != null ? jmsProps.get(JMSConstants.PARAM_JMS_USERNAME) : null; + String pass = jmsProps != null ? jmsProps.get(JMSConstants.PARAM_JMS_PASSWORD) : null; + + QueueConnectionFactory qConFac = null; + TopicConnectionFactory tConFac = null; + + int destType = -1; + if (JMSConstants.DESTINATION_TYPE_QUEUE.equals(jmsOut.getDestinationType())) { + destType = JMSConstants.QUEUE; + qConFac = (QueueConnectionFactory) jmsOut.getConnectionFactory(); + + } else if (JMSConstants.DESTINATION_TYPE_TOPIC.equals(jmsOut.getDestinationType())) { + destType = JMSConstants.TOPIC; + tConFac = (TopicConnectionFactory) jmsOut.getConnectionFactory(); + } + + Connection connection = null; + if (user != null && pass != null) { + if (qConFac != null) { + connection = qConFac.createQueueConnection(user, pass); + } else if (tConFac != null) { + connection = tConFac.createTopicConnection(user, pass); + } + } else { + if (qConFac != null) { + connection = qConFac.createQueueConnection(); + } else if (tConFac != null) { + connection = tConFac.createTopicConnection(); + } + } + + if (connection == null && jmsOut.getJmsConnectionFactory() != null) { + connection = jmsOut.getJmsConnectionFactory().getConnection(); + } + + Session session = null; + MessageProducer producer = null; + Destination destination = jmsOut.getDestination(); + + if (destType == JMSConstants.QUEUE) { + session = ((QueueConnection) connection). + createQueueSession(false, Session.AUTO_ACKNOWLEDGE); + producer = ((QueueSession) session).createSender((Queue) destination); + } else { + session = ((TopicConnection) connection). + createTopicSession(false, Session.AUTO_ACKNOWLEDGE); + producer = ((TopicSession) session).createPublisher((Topic) destination); + } + + return new JMSMessageSender(connection, session, producer, + destination, (jmsOut.getJmsConnectionFactory() == null ? + JMSConstants.CACHE_NONE : jmsOut.getJmsConnectionFactory().getCacheLevel()), false, + destType == -1 ? null : destType == JMSConstants.QUEUE ? Boolean.TRUE : Boolean.FALSE); + } + + /** + * Return a String representation of the destination type + * @param destType the destination type indicator int + * @return a descriptive String + */ + public static String getDestinationTypeAsString(int destType) { + if (destType == JMSConstants.QUEUE) { + return "Queue"; + } else if (destType == JMSConstants.TOPIC) { + return "Topic"; + } else { + return "Generic"; + } + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ServiceTaskManager.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ServiceTaskManager.java new file mode 100644 index 0000000000..28c8da2a8d --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ServiceTaskManager.java @@ -0,0 +1,1217 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.jms; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; + +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.Destination; +import javax.jms.ExceptionListener; +import javax.jms.IllegalStateException; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.Session; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.transaction.NotSupportedException; +import javax.transaction.SystemException; +import javax.transaction.UserTransaction; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.BaseConstants; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.threads.WorkerPool; + +/** + * Each service will have one ServiceTaskManager instance that will create, manage and also destroy + * idle tasks created for it, for message receipt. This will also allow individual tasks to cache + * the Connection, Session or Consumer as necessary, considering the transactionality required and + * user preference. + * + * This also acts as the ExceptionListener for all JMS connections made on behalf of the service. + * Since the ExceptionListener is notified by a JMS provider on a "serious" error, we simply try + * to re-connect. Thus a connection failure for a single task, will re-initialize the state afresh + * for the service, by discarding all connections. + */ +public class ServiceTaskManager { + + /** The logger */ + private static final Log log = LogFactory.getLog(ServiceTaskManager.class); + + /** The Task manager is stopped or has not started */ + private static final int STATE_STOPPED = 0; + /** The Task manager is started and active */ + private static final int STATE_STARTED = 1; + /** The Task manager is paused temporarily */ + private static final int STATE_PAUSED = 2; + /** The Task manager is started, but a shutdown has been requested */ + private static final int STATE_SHUTTING_DOWN = 3; + /** The Task manager has encountered an error */ + private static final int STATE_FAILURE = 4; + + /** The name of the service managed by this instance */ + private String serviceName; + /** The ConnectionFactory MUST refer to an XAConnectionFactory to use JTA */ + private String connFactoryJNDIName; + /** The JNDI name of the Destination Queue or Topic */ + private String destinationJNDIName; + /** JNDI location for the JTA UserTransaction */ + private String userTransactionJNDIName = "java:comp/UserTransaction"; + /** The type of destination - P2P or PubSub (or JMS 1.1 API generic?) */ + private int destinationType = JMSConstants.GENERIC; + /** An optional message selector */ + private String messageSelector = null; + + /** Should tasks run without transactions, using transacted Sessions (i.e. local), or JTA */ + private int transactionality = BaseConstants.TRANSACTION_NONE; + /** Should created Sessions be transactional ? - should be false when using JTA */ + private boolean sessionTransacted = true; + /** Session acknowledgement mode when transacted Sessions (i.e. local transactions) are used */ + private int sessionAckMode = Session.AUTO_ACKNOWLEDGE; + + /** Is the subscription durable ? */ + private boolean subscriptionDurable = false; + /** The name of the durable subscriber for this client */ + private String durableSubscriberName = null; + /** In PubSub mode, should I receive messages sent by me / my connection ? */ + private boolean pubSubNoLocal = false; + /** Number of concurrent consumers - for PubSub, this should be 1 to prevent multiple receipt */ + private int concurrentConsumers = 1; + /** Maximum number of consumers to create - see @concurrentConsumers */ + private int maxConcurrentConsumers = 1; + /** The number of idle (i.e. message-less) attempts to be tried before suicide, to scale down */ + private int idleTaskExecutionLimit = 10; + /** The maximum number of successful message receipts for a task - to limit thread life span */ + private int maxMessagesPerTask = -1; // default is unlimited + /** The default receive timeout - a negative value means wait forever, zero dont wait at all */ + private int receiveTimeout = 1000; + /** JMS Resource cache level - Connection, Session, Consumer. Auto will select safe default */ + private int cacheLevel = JMSConstants.CACHE_AUTO; + /** Should we cache the UserTransaction handle from JNDI - true for almost all app servers */ + private boolean cacheUserTransaction = true; + /** Shared UserTransactionHandle */ + private UserTransaction sharedUserTransaction = null; + /** Should this service use JMS 1.1 ? (when false, defaults to 1.0.2b) */ + private boolean jmsSpec11 = true; + + /** Initial duration to attempt re-connection to JMS provider after failure */ + private int initialReconnectDuration = 10000; + /** Progression factory for geometric series that calculates re-connection times */ + private double reconnectionProgressionFactor = 2.0; // default to [bounded] exponential + /** Upper limit on reconnection attempt duration */ + private long maxReconnectDuration = 1000 * 60 * 60; // 1 hour + + /** The JNDI context properties and other general properties */ + private Hashtable<String,String> jmsProperties = new Hashtable<String, String>(); + /** The JNDI Context acuired */ + private Context context = null; + /** The ConnectionFactory to be used */ + private ConnectionFactory conFactory = null; + /** The JMS Destination */ + private Destination destination = null; + + /** The list of active tasks thats managed by this instance */ + private final List<MessageListenerTask> pollingTasks = + Collections.synchronizedList(new ArrayList<MessageListenerTask>()); + /** The per-service JMS message receiver to be invoked after receipt of messages */ + private JMSMessageReceiver jmsMessageReceiver = null; + + /** State of this Task Manager */ + private volatile int serviceTaskManagerState = STATE_STOPPED; + /** Number of invoker tasks active */ + private volatile int activeTaskCount = 0; + /** The shared thread pool from the Listener */ + private WorkerPool workerPool = null; + + /** The JMS Connection shared between multiple polling tasks - when enabled (reccomended) */ + private Connection sharedConnection = null; + + /** + * Start or re-start the Task Manager by shutting down any existing worker tasks and + * re-creating them. However, if this is STM is PAUSED, a start request is ignored. + * This applies for any connection failures during paused state as well, which then will + * not try to auto recover + */ + public synchronized void start() { + + if (serviceTaskManagerState == STATE_PAUSED) { + log.info("Attempt to re-start paused TaskManager is ignored. Please use resume instead"); + return; + } + + // if any tasks are running, stop whats running now + if (!pollingTasks.isEmpty()) { + stop(); + } + + if (cacheLevel == JMSConstants.CACHE_AUTO) { + cacheLevel = + transactionality == BaseConstants.TRANSACTION_NONE ? + JMSConstants.CACHE_CONSUMER : JMSConstants.CACHE_NONE; + } + switch (cacheLevel) { + case JMSConstants.CACHE_NONE: + log.debug("No JMS resources will be cached/shared between poller " + + "worker tasks of service : " + serviceName); + break; + case JMSConstants.CACHE_CONNECTION: + log.debug("Only the JMS Connection will be cached and shared between *all* " + + "poller task invocations"); + break; + case JMSConstants.CACHE_SESSION: + log.debug("The JMS Connection and Session will be cached and shared between " + + "successive poller task invocations"); + break; + case JMSConstants.CACHE_CONSUMER: + log.debug("The JMS Connection, Session and MessageConsumer will be cached and " + + "shared between successive poller task invocations"); + break; + default : { + handleException("Invalid cache level : " + cacheLevel + + " for service : " + serviceName); + } + } + + for (int i=0; i<concurrentConsumers; i++) { + workerPool.execute(new MessageListenerTask()); + } + + serviceTaskManagerState = STATE_STARTED; + log.info("Task manager for service : " + serviceName + " [re-]initialized"); + } + + /** + * Shutdown the tasks and release any shared resources + */ + public synchronized void stop() { + + if (log.isDebugEnabled()) { + log.debug("Stopping ServiceTaskManager for service : " + serviceName); + } + + if (serviceTaskManagerState != STATE_FAILURE) { + serviceTaskManagerState = STATE_SHUTTING_DOWN; + } + + synchronized(pollingTasks) { + for (MessageListenerTask lstTask : pollingTasks) { + lstTask.requestShutdown(); + } + } + + // try to wait a bit for task shutdown + for (int i=0; i<5; i++) { + if (activeTaskCount == 0) { + break; + } + try { + Thread.sleep(1000); + } catch (InterruptedException ignore) {} + } + + if (sharedConnection != null) { + try { + sharedConnection.stop(); + } catch (JMSException e) { + logError("Error stopping shared Connection", e); + } finally { + sharedConnection = null; + } + } + + if (activeTaskCount > 0) { + log.warn("Unable to shutdown all polling tasks of service : " + serviceName); + } + + if (serviceTaskManagerState != STATE_FAILURE) { + serviceTaskManagerState = STATE_STOPPED; + } + log.info("Task manager for service : " + serviceName + " shutdown"); + } + + /** + * Temporarily suspend receipt and processing of messages. Accomplished by stopping the + * connection / or connections used by the poller tasks + */ + public synchronized void pause() { + for (MessageListenerTask lstTask : pollingTasks) { + lstTask.pause(); + } + if (sharedConnection != null) { + try { + sharedConnection.stop(); + } catch (JMSException e) { + logError("Error pausing shared Connection", e); + } + } + } + + /** + * Resume receipt and processing of messages of paused tasks + */ + public synchronized void resume() { + for (MessageListenerTask lstTask : pollingTasks) { + lstTask.resume(); + } + if (sharedConnection != null) { + try { + sharedConnection.start(); + } catch (JMSException e) { + logError("Error resuming shared Connection", e); + } + } + } + + /** + * Start a new MessageListenerTask if we are still active, the threshold is not reached, and w + * e do not have any idle tasks - i.e. scale up listening + */ + private void scheduleNewTaskIfAppropriate() { + if (serviceTaskManagerState == STATE_STARTED && + pollingTasks.size() < getMaxConcurrentConsumers() && getIdleTaskCount() == 0) { + workerPool.execute(new MessageListenerTask()); + } + } + + /** + * Get the number of MessageListenerTasks that are currently idle + * @return idle task count + */ + private int getIdleTaskCount() { + int count = 0; + for (MessageListenerTask lstTask : pollingTasks) { + if (lstTask.isTaskIdle()) { + count++; + } + } + return count; + } + + /** + * Get the number of MessageListenerTasks that are currently connected to the JMS provider + * @return connected task count + */ + private int getConnectedTaskCount() { + int count = 0; + for (MessageListenerTask lstTask : pollingTasks) { + if (lstTask.isConnected()) { + count++; + } + } + return count; + } + + /** + * The actual threads/tasks that perform message polling + */ + private class MessageListenerTask implements Runnable, ExceptionListener { + + /** The Connection used by the polling task */ + private Connection connection = null; + /** The Sesson used by the polling task */ + private Session session = null; + /** The MessageConsumer used by the polling task */ + private MessageConsumer consumer = null; + /** State of the worker polling task */ + private volatile int workerState = STATE_STOPPED; + /** The number of idle (i.e. without fetching a message) polls for this task */ + private int idleExecutionCount = 0; + /** Is this task idle right now? */ + private volatile boolean idle = false; + /** Is this task connected to the JMS provider successfully? */ + private boolean connected = false; + + /** As soon as we create a new polling task, add it to the STM for control later */ + MessageListenerTask() { + synchronized(pollingTasks) { + pollingTasks.add(this); + } + } + + /** + * Pause this polling worker task + */ + public void pause() { + if (isActive()) { + if (connection != null && cacheLevel < JMSConstants.CACHE_CONNECTION) { + try { + connection.stop(); + } catch (JMSException e) { + log.warn("Error pausing Message Listener task for service : " + serviceName); + } + } + workerState = STATE_PAUSED; + } + } + + /** + * Resume this polling task + */ + public void resume() { + if (connection != null && cacheLevel < JMSConstants.CACHE_CONNECTION) { + try { + connection.start(); + } catch (JMSException e) { + log.warn("Error resuming Message Listener task for service : " + serviceName); + } + } + workerState = STATE_STARTED; + } + + /** + * Execute the polling worker task + */ + public void run() { + workerState = STATE_STARTED; + activeTaskCount++; + int messageCount = 0; + + if (log.isDebugEnabled()) { + log.debug("New poll task starting : thread id = " + Thread.currentThread().getId()); + } + + try { + while (isActive() && + (getMaxMessagesPerTask() < 0 || messageCount < getMaxMessagesPerTask()) && + (getConcurrentConsumers() == 1 || idleExecutionCount < getIdleTaskExecutionLimit())) { + + UserTransaction ut = null; + try { + if (transactionality == BaseConstants.TRANSACTION_JTA) { + ut = getUserTransaction(); + ut.begin(); + } + } catch (NotSupportedException e) { + handleException("Listener Task is already associated with a transaction", e); + } catch (SystemException e) { + handleException("Error starting a JTA transaction", e); + } + + // Get a message by polling, or receive null + Message message = receiveMessage(); + + if (log.isTraceEnabled()) { + if (message != null) { + try { + log.trace("<<<<<<< READ message with Message ID : " + + message.getJMSMessageID() + " from : " + destination + + " by Thread ID : " + Thread.currentThread().getId()); + } catch (JMSException ignore) {} + } else { + log.trace("No message received by Thread ID : " + + Thread.currentThread().getId() + " for destination : " + destination); + } + } + + if (message != null) { + idle = false; + idleExecutionCount = 0; + messageCount++; + // I will be busy now while processing this message, so start another if needed + scheduleNewTaskIfAppropriate(); + handleMessage(message, ut); + + } else { + idle = true; + idleExecutionCount++; + } + } + + } finally { + workerState = STATE_STOPPED; + activeTaskCount--; + synchronized(pollingTasks) { + pollingTasks.remove(this); + } + } + + if (log.isTraceEnabled()) { + log.trace("Listener task with Thread ID : " + Thread.currentThread().getId() + + " is stopping after processing : " + messageCount + " messages :: " + + " isActive : " + isActive() + " maxMessagesPerTask : " + + getMaxMessagesPerTask() + " concurrentConsumers : " + getConcurrentConsumers() + + " idleExecutionCount : " + idleExecutionCount + " idleTaskExecutionLimit : " + + getIdleTaskExecutionLimit()); + } else if (log.isDebugEnabled()) { + log.debug("Listener task with Thread ID : " + Thread.currentThread().getId() + + " is stopping after processing : " + messageCount + " messages"); + } + + closeConsumer(true); + closeSession(true); + closeConnection(); + + // My time is up, so if I am going away, create another + scheduleNewTaskIfAppropriate(); + } + + /** + * Poll for and return a message if available + * + * @return a message read, or null + */ + private Message receiveMessage() { + + // get a new connection, session and consumer to prevent a conflict. + // If idle, it means we can re-use what we already have + if (consumer == null) { + connection = getConnection(); + session = getSession(); + consumer = getMessageConsumer(); + if (log.isDebugEnabled()) { + log.debug("Preparing a Connection, Session and Consumer to read messages"); + } + } + + if (log.isDebugEnabled()) { + log.debug("Waiting for a message for service : " + serviceName + " - duration : " + + (getReceiveTimeout() < 0 ? "unlimited" : (getReceiveTimeout() + "ms"))); + } + + try { + if (getReceiveTimeout() < 0) { + return consumer.receive(); + } else { + return consumer.receive(getReceiveTimeout()); + } + } catch (IllegalStateException ignore) { + // probably the consumer (shared) was closed.. which is still ok.. as we didn't read + } catch (JMSException e) { + logError("Error receiving message for service : " + serviceName, e); + } + return null; + } + + /** + * Invoke ultimate message handler/listener and ack message and/or + * commit/rollback transactions + * @param message the JMS message received + * @param ut the UserTransaction used to receive this message, or null + */ + private void handleMessage(Message message, UserTransaction ut) { + + String messageId = null; + try { + messageId = message.getJMSMessageID(); + } catch (JMSException ignore) {} + + boolean commitOrAck = true; + try { + commitOrAck = jmsMessageReceiver.onMessage(message, ut); + + } finally { + + // if client acknowledgement is selected, and processing requested ACK + if (commitOrAck && getSessionAckMode() == Session.CLIENT_ACKNOWLEDGE) { + try { + message.acknowledge(); + if (log.isDebugEnabled()) { + log.debug("Message : " + messageId + " acknowledged"); + } + } catch (JMSException e) { + logError("Error acknowledging message : " + messageId, e); + } + } + + // close the consumer + closeConsumer(false); + + // if session was transacted, commit it or rollback + try { + if (session.getTransacted()) { + if (commitOrAck) { + session.commit(); + if (log.isDebugEnabled()) { + log.debug("Session for message : " + messageId + " committed"); + } + } else { + session.rollback(); + if (log.isDebugEnabled()) { + log.debug("Session for message : " + messageId + " rolled back"); + } + } + } + } catch (JMSException e) { + logError("Error " + (commitOrAck ? "committing" : "rolling back") + + " local session txn for message : " + messageId, e); + } + + // if a JTA transaction was being used, commit it or rollback + try { + if (ut != null) { + if (commitOrAck) { + ut.commit(); + if (log.isDebugEnabled()) { + log.debug("JTA txn for message : " + messageId + " committed"); + } + } else { + ut.rollback(); + if (log.isDebugEnabled()) { + log.debug("JTA txn for message : " + messageId + " rolled back"); + } + } + } + } catch (Exception e) { + logError("Error " + (commitOrAck ? "committing" : "rolling back") + + " JTA txn for message : " + messageId + " from the session", e); + } + + closeSession(false); + closeConnection(); + } + } + + /** Handle JMS Connection exceptions by re-initializing. A single connection failure could + * cause re-initialization of multiple MessageListenerTasks / Connections + */ + public void onException(JMSException j) { + + if (!isSTMActive()) { + requestShutdown(); + return; + } + + log.warn("JMS Connection failure : " + j.getMessage()); + setConnected(false); + + if (cacheLevel < JMSConstants.CACHE_CONNECTION) { + // failed Connection was not shared, thus no need to restart the whole STM + requestShutdown(); + return; + } + + // if we failed while active, update state to show failure + setServiceTaskManagerState(STATE_FAILURE); + log.error("JMS Connection failed : " + j.getMessage() + " - shutting down worker tasks"); + + int r = 1; + long retryDuration = initialReconnectDuration; + + do { + try { + log.info("Reconnection attempt : " + r + " for service : " + serviceName); + start(); + } catch (Exception ignore) {} + + boolean connected = false; + for (int i=0; i<5; i++) { + if (getConnectedTaskCount() == concurrentConsumers) { + connected = true; + break; + } + try { + Thread.sleep(1000); + } catch (InterruptedException ignore) {} + } + + if (!connected) { + log.error("Reconnection attempt : " + (r++) + " for service : " + serviceName + + " failed. Next retry in " + (retryDuration/1000) + "seconds"); + retryDuration = (long) (retryDuration * reconnectionProgressionFactor); + if (retryDuration > maxReconnectDuration) { + retryDuration = maxReconnectDuration; + } + + try { + Thread.sleep(retryDuration); + } catch (InterruptedException ignore) {} + } + + } while (!isSTMActive() || getConnectedTaskCount() < concurrentConsumers); + } + + protected void requestShutdown() { + workerState = STATE_SHUTTING_DOWN; + } + + private boolean isActive() { + return workerState == STATE_STARTED; + } + + protected boolean isTaskIdle() { + return idle; + } + + public boolean isConnected() { + return connected; + } + + public void setConnected(boolean connected) { + this.connected = connected; + } + + /** + * Get a Connection that could/should be used by this task - depends on the cache level to reuse + * @return the shared Connection if cache level is higher than CACHE_NONE, or a new Connection + */ + private Connection getConnection() { + if (cacheLevel < JMSConstants.CACHE_CONNECTION) { + // Connection is not shared + if (connection == null) { + connection = createConnection(); + } + } else { + if (sharedConnection != null) { + connection = sharedConnection; + } else { + synchronized(this) { + if (sharedConnection == null) { + sharedConnection = createConnection(); + } + connection = sharedConnection; + } + } + } + setConnected(true); + return connection; + } + + /** + * Get a Session that could/should be used by this task - depends on the cache level to reuse + * @param connection the connection (could be the shared connection) to use to create a Session + * @return the shared Session if cache level is higher than CACHE_CONNECTION, or a new Session + * created using the Connection passed, or a new/shared connection + */ + private Session getSession() { + if (session == null || cacheLevel < JMSConstants.CACHE_SESSION) { + session = createSession(); + } + return session; + } + + /** + * Get a MessageConsumer that chould/should be used by this task - depends on the cache + * level to reuse + * @param connection option Connection to be used + * @param session optional Session to be used + * @return the shared MessageConsumer if cache level is higher than CACHE_SESSION, or a new + * MessageConsumer possibly using the Connection and Session passed in + */ + private MessageConsumer getMessageConsumer() { + if (consumer == null || cacheLevel < JMSConstants.CACHE_CONSUMER) { + consumer = createConsumer(); + } + return consumer; + } + + /** + * Close the given Connection, hiding exceptions if any which are logged + * @param connection the Connection to be closed + */ + private void closeConnection() { + if (connection != null && + cacheLevel < JMSConstants.CACHE_CONNECTION) { + try { + if (log.isDebugEnabled()) { + log.debug("Closing non-shared JMS connection for service : " + serviceName); + } + connection.close(); + } catch (JMSException e) { + logError("Error closing JMS connection", e); + } finally { + connection = null; + } + } + } + + /** + * Close the given Session, hiding exceptions if any which are logged + * @param session the Session to be closed + */ + private void closeSession(boolean forced) { + if (session != null && + (cacheLevel < JMSConstants.CACHE_SESSION || forced)) { + try { + if (log.isDebugEnabled()) { + log.debug("Closing non-shared JMS session for service : " + serviceName); + } + session.close(); + } catch (JMSException e) { + logError("Error closing JMS session", e); + } finally { + session = null; + } + } + } + + /** + * Close the given Consumer, hiding exceptions if any which are logged + * @param consumer the Consumer to be closed + */ + private void closeConsumer(boolean forced) { + if (consumer != null && + (cacheLevel < JMSConstants.CACHE_CONSUMER || forced)) { + try { + if (log.isDebugEnabled()) { + log.debug("Closing non-shared JMS consumer for service : " + serviceName); + } + consumer.close(); + } catch (JMSException e) { + logError("Error closing JMS consumer", e); + } finally { + consumer = null; + } + } + } + + /** + * Create a new Connection for this STM, using JNDI properties and credentials provided + * @return a new Connection for this STM, using JNDI properties and credentials provided + */ + private Connection createConnection() { + + try { + conFactory = JMSUtils.lookup( + getInitialContext(), ConnectionFactory.class, getConnFactoryJNDIName()); + log.debug("Connected to the JMS connection factory : " + getConnFactoryJNDIName()); + } catch (NamingException e) { + handleException("Error looking up connection factory : " + getConnFactoryJNDIName() + + " using JNDI properties : " + jmsProperties, e); + } + + Connection connection = null; + try { + connection = JMSUtils.createConnection( + conFactory, + jmsProperties.get(JMSConstants.PARAM_JMS_USERNAME), + jmsProperties.get(JMSConstants.PARAM_JMS_PASSWORD), + isJmsSpec11(), isQueue()); + + connection.setExceptionListener(this); + connection.start(); + log.debug("JMS Connection for service : " + serviceName + " created and started"); + + } catch (JMSException e) { + handleException("Error acquiring a JMS connection to : " + getConnFactoryJNDIName() + + " using JNDI properties : " + jmsProperties, e); + } + return connection; + } + + /** + * Create a new Session for this STM + * @param connection the Connection to be used + * @return a new Session created using the Connection passed in + */ + private Session createSession() { + try { + if (log.isDebugEnabled()) { + log.debug("Creating a new JMS Session for service : " + serviceName); + } + return JMSUtils.createSession( + connection, isSessionTransacted(), getSessionAckMode(), isJmsSpec11(), isQueue()); + + } catch (JMSException e) { + handleException("Error creating JMS session for service : " + serviceName, e); + } + return null; + } + + /** + * Create a new MessageConsumer for this STM + * @param session the Session to be used + * @return a new MessageConsumer created using the Session passed in + */ + private MessageConsumer createConsumer() { + try { + if (log.isDebugEnabled()) { + log.debug("Creating a new JMS MessageConsumer for service : " + serviceName); + } + + return JMSUtils.createConsumer( + session, getDestination(session), isQueue(), + (isSubscriptionDurable() && getDurableSubscriberName() == null ? + getDurableSubscriberName() : serviceName), + getMessageSelector(), isPubSubNoLocal(), isSubscriptionDurable(), isJmsSpec11()); + + } catch (JMSException e) { + handleException("Error creating JMS consumer for service : " + serviceName,e); + } + return null; + } + } + + // -------------- mundane private methods ---------------- + /** + * Get the InitialContext for lookup using the JNDI parameters applicable to the service + * @return the InitialContext to be used + * @throws NamingException + */ + private Context getInitialContext() throws NamingException { + if (context == null) { + context = new InitialContext(jmsProperties); + } + return context; + } + + /** + * Return the JMS Destination for the JNDI name of the Destination from the InitialContext + * @return the JMS Destination to which this STM listens for messages + */ + private Destination getDestination(Session session) { + if (destination == null) { + try { + context = getInitialContext(); + destination = JMSUtils.lookup(context, Destination.class, getDestinationJNDIName()); + if (log.isDebugEnabled()) { + log.debug("JMS Destination with JNDI name : " + getDestinationJNDIName() + + " found for service " + serviceName); + } + } catch (NamingException e) { + try { + switch (destinationType) { + case JMSConstants.QUEUE: { + destination = session.createQueue(getDestinationJNDIName()); + break; + } + case JMSConstants.TOPIC: { + destination = session.createTopic(getDestinationJNDIName()); + break; + } + default: { + handleException("Error looking up JMS destination : " + + getDestinationJNDIName() + " using JNDI properties : " + + jmsProperties, e); + } + } + } catch (JMSException j) { + handleException("Error looking up and creating JMS destination : " + + getDestinationJNDIName() + " using JNDI properties : " + jmsProperties, e); + } + } + } + return destination; + } + + /** + * The UserTransaction to be used, looked up from the JNDI + * @return The UserTransaction to be used, looked up from the JNDI + */ + private UserTransaction getUserTransaction() { + if (!cacheUserTransaction) { + if (log.isDebugEnabled()) { + log.debug("Acquiring a new UserTransaction for service : " + serviceName); + } + + try { + context = getInitialContext(); + return + JMSUtils.lookup(context, UserTransaction.class, getUserTransactionJNDIName()); + } catch (NamingException e) { + handleException("Error looking up UserTransaction : " + getDestinationJNDIName() + + " using JNDI properties : " + jmsProperties, e); + } + } + + if (sharedUserTransaction == null) { + try { + context = getInitialContext(); + sharedUserTransaction = + JMSUtils.lookup(context, UserTransaction.class, getUserTransactionJNDIName()); + if (log.isDebugEnabled()) { + log.debug("Acquired shared UserTransaction for service : " + serviceName); + } + } catch (NamingException e) { + handleException("Error looking up UserTransaction : " + getDestinationJNDIName() + + " using JNDI properties : " + jmsProperties, e); + } + } + return sharedUserTransaction; + } + + // -------------------- trivial methods --------------------- + private boolean isSTMActive() { + return serviceTaskManagerState == STATE_STARTED; + } + + /** + * Is this STM bound to a Queue, Topic or a JMS 1.1 Generic Destination? + * @return TRUE for a Queue, FALSE for a Topic and NULL for a Generic Destination + */ + private Boolean isQueue() { + if (destinationType == JMSConstants.GENERIC) { + return null; + } else { + return destinationType == JMSConstants.QUEUE; + } + } + + private void logError(String msg, Exception e) { + log.error(msg, e); + } + + private void handleException(String msg, Exception e) { + log.error(msg, e); + throw new AxisJMSException(msg, e); + } + + private void handleException(String msg) { + log.error(msg); + throw new AxisJMSException(msg); + } + + // -------------- getters and setters ------------------ + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public String getConnFactoryJNDIName() { + return connFactoryJNDIName; + } + + public void setConnFactoryJNDIName(String connFactoryJNDIName) { + this.connFactoryJNDIName = connFactoryJNDIName; + } + + public String getDestinationJNDIName() { + return destinationJNDIName; + } + + public void setDestinationJNDIName(String destinationJNDIName) { + this.destinationJNDIName = destinationJNDIName; + } + + public int getDestinationType() { + return destinationType; + } + + public void setDestinationType(int destinationType) { + this.destinationType = destinationType; + } + + public String getMessageSelector() { + return messageSelector; + } + + public void setMessageSelector(String messageSelector) { + this.messageSelector = messageSelector; + } + + public int getTransactionality() { + return transactionality; + } + + public void setTransactionality(int transactionality) { + this.transactionality = transactionality; + sessionTransacted = (transactionality == BaseConstants.TRANSACTION_LOCAL); + } + + public boolean isSessionTransacted() { + return sessionTransacted; + } + + public void setSessionTransacted(Boolean sessionTransacted) { + if (sessionTransacted != null) { + this.sessionTransacted = sessionTransacted; + // sesstionTransacted means local transactions are used, however !sessionTransacted does + // not mean that JTA is used + if (sessionTransacted) { + transactionality = BaseConstants.TRANSACTION_LOCAL; + } + } + } + + public int getSessionAckMode() { + return sessionAckMode; + } + + public void setSessionAckMode(int sessionAckMode) { + this.sessionAckMode = sessionAckMode; + } + + public boolean isSubscriptionDurable() { + return subscriptionDurable; + } + + public void setSubscriptionDurable(Boolean subscriptionDurable) { + if (subscriptionDurable != null) { + this.subscriptionDurable = subscriptionDurable; + } + } + + public String getDurableSubscriberName() { + return durableSubscriberName; + } + + public void setDurableSubscriberName(String durableSubscriberName) { + this.durableSubscriberName = durableSubscriberName; + } + + public boolean isPubSubNoLocal() { + return pubSubNoLocal; + } + + public void setPubSubNoLocal(Boolean pubSubNoLocal) { + if (pubSubNoLocal != null) { + this.pubSubNoLocal = pubSubNoLocal; + } + } + + public int getConcurrentConsumers() { + return concurrentConsumers; + } + + public void setConcurrentConsumers(int concurrentConsumers) { + this.concurrentConsumers = concurrentConsumers; + } + + public int getMaxConcurrentConsumers() { + return maxConcurrentConsumers; + } + + public void setMaxConcurrentConsumers(int maxConcurrentConsumers) { + this.maxConcurrentConsumers = maxConcurrentConsumers; + } + + public int getIdleTaskExecutionLimit() { + return idleTaskExecutionLimit; + } + + public void setIdleTaskExecutionLimit(int idleTaskExecutionLimit) { + this.idleTaskExecutionLimit = idleTaskExecutionLimit; + } + + public int getReceiveTimeout() { + return receiveTimeout; + } + + public void setReceiveTimeout(int receiveTimeout) { + this.receiveTimeout = receiveTimeout; + } + + public int getCacheLevel() { + return cacheLevel; + } + + public void setCacheLevel(int cacheLevel) { + this.cacheLevel = cacheLevel; + } + + public int getInitialReconnectDuration() { + return initialReconnectDuration; + } + + public void setInitialReconnectDuration(int initialReconnectDuration) { + this.initialReconnectDuration = initialReconnectDuration; + } + + public double getReconnectionProgressionFactor() { + return reconnectionProgressionFactor; + } + + public void setReconnectionProgressionFactor(double reconnectionProgressionFactor) { + this.reconnectionProgressionFactor = reconnectionProgressionFactor; + } + + public long getMaxReconnectDuration() { + return maxReconnectDuration; + } + + public void setMaxReconnectDuration(long maxReconnectDuration) { + this.maxReconnectDuration = maxReconnectDuration; + } + + public int getMaxMessagesPerTask() { + return maxMessagesPerTask; + } + + public void setMaxMessagesPerTask(int maxMessagesPerTask) { + this.maxMessagesPerTask = maxMessagesPerTask; + } + + public String getUserTransactionJNDIName() { + return userTransactionJNDIName; + } + + public void setUserTransactionJNDIName(String userTransactionJNDIName) { + if (userTransactionJNDIName != null) { + this.userTransactionJNDIName = userTransactionJNDIName; + } + } + + public boolean isCacheUserTransaction() { + return cacheUserTransaction; + } + + public void setCacheUserTransaction(Boolean cacheUserTransaction) { + if (cacheUserTransaction != null) { + this.cacheUserTransaction = cacheUserTransaction; + } + } + + public boolean isJmsSpec11() { + return jmsSpec11; + } + + public void setJmsSpec11(boolean jmsSpec11) { + this.jmsSpec11 = jmsSpec11; + } + + public Hashtable<String, String> getJmsProperties() { + return jmsProperties; + } + + public void addJmsProperties(Map<String, String> jmsProperties) { + this.jmsProperties.putAll(jmsProperties); + } + + public void removeJmsProperties(String key) { + this.jmsProperties.remove(key); + } + + public Context getContext() { + return context; + } + + public ConnectionFactory getConnectionFactory() { + return conFactory; + } + + public List<MessageListenerTask> getPollingTasks() { + return pollingTasks; + } + + public void setJmsMessageReceiver(JMSMessageReceiver jmsMessageReceiver) { + this.jmsMessageReceiver = jmsMessageReceiver; + } + + public void setWorkerPool(WorkerPool workerPool) { + this.workerPool = workerPool; + } + + public int getActiveTaskCount() { + return activeTaskCount; + } + + public void setServiceTaskManagerState(int serviceTaskManagerState) { + this.serviceTaskManagerState = serviceTaskManagerState; + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/ContentTypeInfo.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/ContentTypeInfo.java new file mode 100644 index 0000000000..2b415df64d --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/ContentTypeInfo.java @@ -0,0 +1,49 @@ +/* +* Copyright 2004,2005 The Apache Software Foundation. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.jms.ctype; + +/** + * Class encapsulating the content type information for a given message. + */ +public class ContentTypeInfo { + private final String propertyName; + private final String contentType; + + public ContentTypeInfo(String propertyName, String contentType) { + this.propertyName = propertyName; + this.contentType = contentType; + } + + /** + * Get the name of the message property from which the content type + * has been extracted. + * + * @return the property name or null if the content type was not determined + * by extracting it from a message property + */ + public String getPropertyName() { + return propertyName; + } + + /** + * Get the content type of the message. + * + * @return The content type of the message. The return value is never null. + */ + public String getContentType() { + return contentType; + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/ContentTypeRule.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/ContentTypeRule.java new file mode 100644 index 0000000000..0dba93356f --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/ContentTypeRule.java @@ -0,0 +1,43 @@ +/* +* Copyright 2004,2005 The Apache Software Foundation. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.jms.ctype; + +import javax.jms.JMSException; +import javax.jms.Message; + +/** + * Interface implemented by content type rules. + */ +public interface ContentTypeRule { + /** + * Attempt to determine the content type of the given JMS message. + * + * @param message the message + * @return If the rule matches, the return value encapsulates the content type of the + * message and the message property name from which is was extracted + * (if applicable). If the rule doesn't match, the method returns null. + * @throws JMSException + */ + ContentTypeInfo getContentType(Message message) throws JMSException; + + /** + * Get the name of the message property used to extract the content type from, + * if applicable. + * + * @return the property name or null if not applicable + */ + String getExpectedContentTypeProperty(); +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/ContentTypeRuleFactory.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/ContentTypeRuleFactory.java new file mode 100644 index 0000000000..a9fd25ef1b --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/ContentTypeRuleFactory.java @@ -0,0 +1,74 @@ +/* +* Copyright 2004,2005 The Apache Software Foundation. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.jms.ctype; + +import java.util.Iterator; + +import javax.jms.BytesMessage; +import javax.jms.TextMessage; + +import org.apache.axiom.om.OMElement; +import org.apache.axis2.AxisFault; +import org.apache.axis2.description.Parameter; + +/** + * Utility class to create content type rules and rule sets from XML. + */ +public class ContentTypeRuleFactory { + private ContentTypeRuleFactory() {} + + public static ContentTypeRule parse(OMElement element) throws AxisFault { + String name = element.getLocalName(); + String value = element.getText(); + if (name.equals("jmsProperty")) { + return new PropertyRule(value); + } else if (name.equals("textMessage")) { + return new MessageTypeRule(TextMessage.class, value); + } else if (name.equals("bytesMessage")) { + return new MessageTypeRule(BytesMessage.class, value); + } else if (name.equals("default")) { + return new DefaultRule(value); + } else { + throw new AxisFault("Unknown content rule type '" + name + "'"); + } + } + + public static ContentTypeRuleSet parse(Parameter param) throws AxisFault { + ContentTypeRuleSet ruleSet = new ContentTypeRuleSet(); + Object value = param.getValue(); + if (value instanceof OMElement) { + OMElement element = (OMElement)value; + + // DescriptionBuilder#processParameters actually sets the parameter element + // itself as the value. We need to support this case. + // TODO: seems like a bug in Axis2 and is inconsistent with Synapse's way of parsing parameter in proxy definitions + if (element == param.getParameterElement()) { + element = element.getFirstElement(); + } + + if (element.getLocalName().equals("rules")) { + for (Iterator it = element.getChildElements(); it.hasNext(); ) { + ruleSet.addRule(parse((OMElement)it.next())); + } + } else { + throw new AxisFault("Expected <rules> element"); + } + } else { + ruleSet.addRule(new DefaultRule((String)value)); + } + return ruleSet; + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/ContentTypeRuleSet.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/ContentTypeRuleSet.java new file mode 100644 index 0000000000..90383a42f8 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/ContentTypeRuleSet.java @@ -0,0 +1,64 @@ +/* +* Copyright 2004,2005 The Apache Software Foundation. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.jms.ctype; + +import java.util.ArrayList; +import java.util.List; + +import javax.jms.JMSException; +import javax.jms.Message; + +/** + * A set of content type rules. + */ +public class ContentTypeRuleSet { + private final List<ContentTypeRule> rules = new ArrayList<ContentTypeRule>(); + private String defaultContentTypeProperty; + + /** + * Add a content type rule to this set. + * + * @param rule the rule to add + */ + public void addRule(ContentTypeRule rule) { + rules.add(rule); + if (defaultContentTypeProperty == null) { + defaultContentTypeProperty = rule.getExpectedContentTypeProperty(); + } + } + + /** + * Determine the content type of the given message. + * This method will try the registered rules in turn until the first rule matches. + * + * @param message the message + * @return the content type information for the message or null if none of the rules matches + * @throws JMSException + */ + public ContentTypeInfo getContentTypeInfo(Message message) throws JMSException { + for (ContentTypeRule rule : rules) { + ContentTypeInfo contentTypeInfo = rule.getContentType(message); + if (contentTypeInfo != null) { + return contentTypeInfo; + } + } + return null; + } + + public String getDefaultContentTypeProperty() { + return defaultContentTypeProperty; + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/DefaultRule.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/DefaultRule.java new file mode 100644 index 0000000000..a158f6ec74 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/DefaultRule.java @@ -0,0 +1,37 @@ +/* +* Copyright 2004,2005 The Apache Software Foundation. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.jms.ctype; + +import javax.jms.Message; + +/** + * Content type rule that always matches and that returns a fixed (default) content type. + */ +public class DefaultRule implements ContentTypeRule { + private final String contentType; + + public DefaultRule(String contentType) { + this.contentType = contentType; + } + + public ContentTypeInfo getContentType(Message message) { + return new ContentTypeInfo(null, contentType); + } + + public String getExpectedContentTypeProperty() { + return null; + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/MessageTypeRule.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/MessageTypeRule.java new file mode 100644 index 0000000000..cb25ab93d4 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/MessageTypeRule.java @@ -0,0 +1,39 @@ +/* +* Copyright 2004,2005 The Apache Software Foundation. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.jms.ctype; + +import javax.jms.Message; + +/** + * Content type rule that matches a given message type and returns a fixed content type. + */ +public class MessageTypeRule implements ContentTypeRule { + private final Class<? extends Message> messageType; + private final String contentType; + + public MessageTypeRule(Class<? extends Message> messageType, String contentType) { + this.messageType = messageType; + this.contentType = contentType; + } + + public ContentTypeInfo getContentType(Message message) { + return messageType.isInstance(message) ? new ContentTypeInfo(null, contentType) : null; + } + + public String getExpectedContentTypeProperty() { + return null; + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/PropertyRule.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/PropertyRule.java new file mode 100644 index 0000000000..c8d13ba462 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/PropertyRule.java @@ -0,0 +1,39 @@ +/* +* Copyright 2004,2005 The Apache Software Foundation. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.jms.ctype; + +import javax.jms.JMSException; +import javax.jms.Message; + +/** + * Content type rule that attempts to extract the content type from a message property. + */ +public class PropertyRule implements ContentTypeRule { + private final String propertyName; + + public PropertyRule(String propertyName) { + this.propertyName = propertyName; + } + + public ContentTypeInfo getContentType(Message message) throws JMSException { + String value = message.getStringProperty(propertyName); + return value == null ? null : new ContentTypeInfo(propertyName, value); + } + + public String getExpectedContentTypeProperty() { + return propertyName; + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/package-info.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/package-info.java new file mode 100644 index 0000000000..750170edd7 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/ctype/package-info.java @@ -0,0 +1,23 @@ +/* +* Copyright 2004,2005 The Apache Software Foundation. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +/** + * Provides classes and interfaces to define content type rules. + * + * Content type rules are used to determine the content type of a + * received message based on JMS properties, message type, etc. + */ +package org.apache.tuscany.sca.binding.ws.axis2.jms.ctype;
\ No newline at end of file diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/package.html b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/package.html new file mode 100644 index 0000000000..b95b49eb69 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/jms/package.html @@ -0,0 +1,356 @@ +<html> +<title>JMS Transport Configuration</title> +<body> + +<h2>JMS Listener Configuration (axis2.xml)</h2> + +e.g: + +<pre> + <transportReceiver name="jms" class="org.apache.axis2.transport.jms.JMSListener"> + <parameter name="myTopicConnectionFactory"> + <parameter name="java.naming.factory.initial">org.apache.activemq.jndi.ActiveMQInitialContextFactory</parameter> + <parameter name="java.naming.provider.url">tcp://localhost:61616</parameter> + <parameter name="transport.jms.ConnectionFactoryJNDIName">TopicConnectionFactory</parameter> + <parameter name="transport.jms.ConnectionFactoryType">topic</parameter> + <parameter name="transport.jms.JMSSpecVersion">1.0.2b</parameter> + </parameter> + + <parameter name="myQueueConnectionFactory"> + <parameter name="java.naming.factory.initial">org.apache.activemq.jndi.ActiveMQInitialContextFactory</parameter> + <parameter name="java.naming.provider.url">tcp://localhost:61616</parameter> + <parameter name="transport.jms.ConnectionFactoryJNDIName">QueueConnectionFactory</parameter> + <parameter name="transport.jms.ConnectionFactoryType">queue</parameter> + <parameter name="transport.jms.JMSSpecVersion">1.1</parameter> + </parameter> + + <parameter name="default"> + <parameter name="java.naming.factory.initial">org.apache.activemq.jndi.ActiveMQInitialContextFactory</parameter> + <parameter name="java.naming.provider.url">tcp://localhost:61616</parameter> + <parameter name="transport.jms.ConnectionFactoryJNDIName">ConnectionFactory</parameter> + </parameter> + </transportReceiver> + + <transportSender name="jms" class="org.apache.axis2.transport.jms.JMSSender"> + <parameter name="myTopicConnectionFactory"> + <parameter name="java.naming.factory.initial">org.apache.activemq.jndi.ActiveMQInitialContextFactory</parameter> + <parameter name="java.naming.provider.url">tcp://localhost:61616</parameter> + <parameter name="transport.jms.ConnectionFactoryJNDIName">TopicConnectionFactory</parameter> + <parameter name="transport.jms.JMSSpecVersion">1.0.2b</parameter> + <parameter name="transport.jms.CacheLevel">producer</parameter> + </parameter> + + <parameter name="myQueueConnectionFactory"> + <parameter name="java.naming.factory.initial">org.apache.activemq.jndi.ActiveMQInitialContextFactory</parameter> + <parameter name="java.naming.provider.url">tcp://localhost:61616</parameter> + <parameter name="transport.jms.ConnectionFactoryJNDIName">QueueConnectionFactory</parameter> + <parameter name="transport.jms.JMSSpecVersion">1.0.2b</parameter> + <parameter name="transport.jms.CacheLevel">producer</parameter> + </parameter> + + <parameter name="default"> + <parameter name="java.naming.factory.initial">org.apache.activemq.jndi.ActiveMQInitialContextFactory</parameter> + <parameter name="java.naming.provider.url">tcp://localhost:61616</parameter> + <parameter name="transport.jms.ConnectionFactoryJNDIName">ConnectionFactory</parameter> + <parameter name="transport.jms.CacheLevel">connection</parameter> + </parameter> + </transportSender> +</pre> + +<p> + The Transport Listener and Sender both allows the user to configure one or more logical JMS Connection + Factories, which are named definitions as shown above. Any remaining parameters maybe defined at the + service level via the services.xml. The applicable set of parameters for a service would be the + union of the properties from the services.xml and the corresponding logical connection factory. +</p> + +<TABLE WIDTH="100%" BORDER=1 BORDERCOLOR="#000000" CELLPADDING=4 CELLSPACING=0> + <COL WIDTH="10%"> + <COL WIDTH="20%"> + <COL WIDTH="60%"> + <COL WIDTH="5%"> + <COL WIDTH="5%"> + <tr> + <td>Transport level</td> + <td><BR></td> + <td><BR></td> + <td>Listening</td> + <td>Sending</td> + </tr> + <tr> + <td>JNDI</td> + <td>java.naming.factory.initial</td> + <td>The JNDI InitialContext factory class</td> + <td>Required</td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>java.naming.provider.url</td> + <td>JNDI Provider URL</td> + <td>Required</td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>java.naming.security.principal</td> + <td>Username for JNDI access</td> + <td><BR></td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>java.naming.security.credentials</td> + <td>Password for JNDI access</td> + <td><BR></td> + <td><BR></td> + </tr> + <tr> + <td>Transactions</td> + <td>transport.Transactionality</td> + <td>Desired transactionality. One of none / local / jta</td> + <td>Defaults to <B>none</B></td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.UserTxnJNDIName</td> + <td>JNDI name to be used to obtain a UserTransaction</td> + <td>Defaults to "java:comp/UserTransaction"</td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.CacheUserTxn</td> + <td>Generally its safe and more efficient to cache the + UserTransaction reference from JNDI. One of true/ false</td> + <td>Defaults to <B>true</B></td> + <td><BR></td> + </tr> + + <tr> + <td><BR></td> + <td>transport.jms.SessionTransacted</td> + <td>Should the JMS Session be transacted. One of true/ false</td> + <td>Defaults to <B>true</B> when local transactions are used</td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.jms.SessionAcknowledgement</td> + <td>JMS Session acknowledgement mode to be used. One of AUTO_ACKNOWLEDGE | CLIENT_ACKNOWLEDGE | DUPS_OK_ACKNOWLEDGE | SESSION_TRANSACTED</td> + <td>Defaults to <B>AUTO_ACKNOWLEDGE</B></td> + <td><BR></td> + </tr> + + <tr> + <td>Connection</td> + <td>transport.jms.ConnectionFactory</td> + <td>Name of the logical connection factory this service will use</td> + <td>Defaults to "default"</td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.jms.ConnectionFactoryJNDIName</td> + <td>The JNDI name of the JMS ConnectionFactory</td> + <td>Required</td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.jms.ConnectionFactoryType</td> + <td> Type of ConnectionFactory – queue / topic</td> + <td>Suggested to be specified</td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.jms.JMSSpecVersion</td> + <td>JMS API Version One of "1.1" or "1.0.2b"</td> + <td>Defaults to 1.1</td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.jms.UserName</td> + <td>The JMS connection username</td> + <td><BR></td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.jms.Password</td> + <td>The JMS connection password</td> + <td><BR></td> + <td><BR></td> + </tr> + <tr> + <td>Destinations</td> + <td>transport.jms.Destination</td> + <td>JNDI Name of the Destination </td> + <td>Defaults to Service name</td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.jms.DestinationType</td> + <td>Type of Destination – queue / topic</td> + <td>Defaults to a queue</td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.jms.DefaultReplyDestination</td> + <td>JNDI Name of the default reply Destination</td> + <td><BR></td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.jms.DefaultReplyDestinationType</td> + <td>Type of the reply Destination – queue / topic</td> + <td>Same type as of Destination</td> + <td><BR></td> + </tr> + <tr> + <td>Advanced</td> + <td>transport.jms.MessageSelector</td> + <td>Optional message selector to be applied</td> + <td><BR></td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.jms.SubscriptionDurable</td> + <td>Is the subscription durable? (For Topics) – true / false</td> + <td>Defaults to <B>false</B></td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.jms.DurableSubscriberName</td> + <td>Name to be used for the durable subscription</td> + <td>Required when subscription is durable</td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.jms.PubSubNoLocal</td> + <td>Should messages published by the same connection (for Topics) + be received? – true / false</td> + <td>Defaults to <B>false</B></td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.jms.CacheLevel</td> + <td>The JMS resource cache level. One of none / connection / + session / consumer / producer / auto</td> + <td>Defaults to <B>auto</B> </td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.jms.ReceiveTimeout</td> + <td>Time to wait for a JMS message during polling. Negative means + wait forever, while 0 means do not wait at all. Anything else, is + a millisecond value for the poll</td> + <td>Defaults to 1000ms</td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.jms.ConcurrentConsumers</td> + <td>Number of concurrent consumer tasks (~threads) to be started to + poll for messages for this service. For Topics, this should be + always 1, to prevent the same message being processed multiple + times</td> + <td>Defaults to <B>1</B></td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.jms.MaxConcurrentConsumers</td> + <td>Will dynamically scale the number of concurrent consumer tasks + (~threads) until this value; as the load increases. Should always + be 1 for Topics.</td> + <td>Defaults to <B>1</B></td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.jms.IdleTaskLimit</td> + <td>The number of idle (i.e. poll without receipt of a message) + runs per task, before it dies – to recycle resources, and to + allow dynamic scale down.</td> + <td>Defaults to 10</td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.jms.MaxMessagesPerTask</td> + <td>The maximum number of successful message receipts to limit per + Task lifetime. </td> + <td>Defaults to <B>–1</B> which implies unlimited messages</td> + <td><BR></td> + </tr> + <tr> + <td>Reconnection</td> + <td>transport.jms.InitialReconnectDuration</td> + <td>Initial reconnection attempt duration</td> + <td>Defaults to 10,000ms</td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.jms.ReconnectProgressFactor</td> + <td>Factor used to compute consecutive reconnection attempt + durations, in a geometric series</td> + <td>Defaults to <B>2 (i.e. exponential)</B></td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.jms.MaxReconnectDuration</td> + <td>Maximum limit for a reconnection duration</td> + <td>Defaults to <B>1 hour</B></td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>transport.jms.PublishEPR</td> + <td>One or more JMS URL's to be showed as the JMS EPRs on the WSDL + for the service. Allows the specification of a custom EPR, and/or + hiding of internal properties from a public EPR (e.g. + credentials). Add one as LEGACY to retain auto generated EPR, when + adding new EPRs</td> + <td><BR></td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td><BR></td> + <td><BR></td> + <td><BR></td> + <td><BR></td> + </tr> + <tr> + <td>Legacy Mode and Payload handling</td> + <td>Wrapper</td> + <td>Binary and Text payload wrapper element to be specified as "{ns}name" where ns refers to a namespace and name the name of the element</td> + <td>Default binary wrapper<ul><li>{http://ws.apache.org/commons/ns/payload}binary</li></ul> + Default text wrapper <ul><li>{http://ws.apache.org/commons/ns/payload}text</li></ul></td> + <td><BR></td> + </tr> + <tr> + <td><BR></td> + <td>Operation</td> + <td>operation name to be specified as "{ns}name" where ns refers to the namespace and name the name of the operation</td> + <td>Defaults to urn:mediate</td> + <td><BR></td> + </tr> +</TABLE> + +</body> +</html>
\ No newline at end of file diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/AbstractPollTableEntry.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/AbstractPollTableEntry.java new file mode 100644 index 0000000000..fa64f08a25 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/AbstractPollTableEntry.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.transport.base; + +import java.util.TimerTask; + +import org.apache.axis2.addressing.EndpointReference; +import org.apache.axis2.description.AxisService; + +public abstract class AbstractPollTableEntry { + // status of last scan + public static final int SUCCSESSFUL = 0; + public static final int WITH_ERRORS = 1; + public static final int FAILED = 2; + public static final int NONE = 3; + + /** Axis2 service */ + private AxisService service; + /** next poll time */ + private long nextPollTime; + /** last poll performed at */ + private long lastPollTime; + /** duration in ms between successive polls */ + private long pollInterval; + /** state of the last poll */ + private int lastPollState; + /** can polling occur in parallel? */ + private boolean concurrentPollingAllowed = false; + /** The timer task that will trigger the next poll */ + TimerTask timerTask; + /** Flag indicating whether polling has been canceled. */ + boolean canceled; + + public AxisService getService() { + return service; + } + + void setService(AxisService service) { + this.service = service; + } + + public abstract EndpointReference getEndpointReference(); + + public long getNextPollTime() { + return nextPollTime; + } + + public void setNextPollTime(long nextPollTime) { + this.nextPollTime = nextPollTime; + } + + public long getLastPollTime() { + return lastPollTime; + } + + public void setLastPollTime(long lastPollTime) { + this.lastPollTime = lastPollTime; + } + + public long getPollInterval() { + return pollInterval; + } + + public void setPollInterval(long pollInterval) { + this.pollInterval = pollInterval; + } + + public int getLastPollState() { + return lastPollState; + } + + public void setLastPollState(int lastPollState) { + this.lastPollState = lastPollState; + } + + public boolean isConcurrentPollingAllowed() { + return concurrentPollingAllowed; + } + + public void setConcurrentPollingAllowed(boolean concurrentPollingAllowed) { + this.concurrentPollingAllowed = concurrentPollingAllowed; + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/AbstractPollingTransportListener.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/AbstractPollingTransportListener.java new file mode 100644 index 0000000000..0ee9d92443 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/AbstractPollingTransportListener.java @@ -0,0 +1,267 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.transport.base; + +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import org.apache.axis2.AxisFault; +import org.apache.axis2.addressing.EndpointReference; +import org.apache.axis2.context.ConfigurationContext; +import org.apache.axis2.description.AxisService; +import org.apache.axis2.description.Parameter; +import org.apache.axis2.description.ParameterInclude; +import org.apache.axis2.description.TransportInDescription; + +public abstract class AbstractPollingTransportListener<T extends AbstractPollTableEntry> + extends AbstractTransportListener { + + /** The main timer. */ + private Timer timer; + /** Keep the list of endpoints and poll durations */ + private final List<T> pollTable = new ArrayList<T>(); + + @Override + public void init(ConfigurationContext cfgCtx, + TransportInDescription transportIn) throws AxisFault { + + timer = new Timer("PollTimer"); + super.init(cfgCtx, transportIn); + T entry = createPollTableEntry(transportIn); + if (entry != null) { + entry.setPollInterval(getPollInterval(transportIn)); + schedulePoll(entry); + pollTable.add(entry); + } + } + + @Override + public void destroy() { + // Explicitly cancel all polls not predispatched to services. All other polls will + // be canceled by stopListeningForService. Pay attention to the fact the cancelPoll + // modifies pollTable. + List<T> entriesToCancel = new ArrayList<T>(); + for (T entry : pollTable) { + if (entry.getService() == null) { + entriesToCancel.add(entry); + } + } + for (T entry : entriesToCancel) { + cancelPoll(entry); + } + + super.destroy(); + timer.cancel(); + timer = null; + } + + /** + * Schedule a repeated poll at the specified interval for a given service. + * The method will schedule a single-shot timer task with executes a work + * task on the worker pool. At the end of this work task, a new timer task + * is scheduled for the next poll (except if the polling for the service + * has been canceled). This effectively schedules the poll repeatedly + * with fixed delay. + * @param entry the poll table entry with the configuration for the service + * @param pollInterval the interval between successive polls in milliseconds + */ + void schedulePoll(final T entry) { + final long pollInterval = entry.getPollInterval(); + TimerTask timerTask = new TimerTask() { + @Override + public void run() { + workerPool.execute(new Runnable() { + public void run() { + if (state == BaseConstants.PAUSED) { + if (log.isDebugEnabled()) { + log.debug("Transport " + getTransportName() + + " poll trigger : Transport is currently paused.."); + } + } else { + poll(entry); + } + } + }); + } + }; + entry.timerTask = timerTask; + if (entry.isConcurrentPollingAllowed()) { + timer.scheduleAtFixedRate(timerTask, pollInterval, pollInterval); + } else { + timer.schedule(timerTask, pollInterval); + } + } + + private void cancelPoll(T entry) { + synchronized (entry) { + entry.timerTask.cancel(); + entry.canceled = true; + } + pollTable.remove(entry); + } + + protected abstract void poll(T entry); + + protected void onPollCompletion(T entry) { + if (!entry.isConcurrentPollingAllowed()) { + synchronized (entry) { + if (!entry.canceled) { + schedulePoll(entry); + } + } + } + } + + /** + * method to log a failure to the log file and to update the last poll status and time + * @param msg text for the log message + * @param e optional exception encountered or null + * @param entry the PollTableEntry + */ + protected void processFailure(String msg, Exception e, T entry) { + if (e == null) { + log.error(msg); + } else { + log.error(msg, e); + } + long now = System.currentTimeMillis(); + entry.setLastPollState(AbstractPollTableEntry.FAILED); + entry.setLastPollTime(now); + entry.setNextPollTime(now + entry.getPollInterval()); + onPollCompletion(entry); + } + + private long getPollInterval(ParameterInclude params) { + Parameter param = params.getParameter(BaseConstants.TRANSPORT_POLL_INTERVAL); + long pollInterval = BaseConstants.DEFAULT_POLL_INTERVAL; + if (param != null && param.getValue() instanceof String) { + String s = (String)param.getValue(); + int multiplier; + if (s.endsWith("ms")) { + s = s.substring(0, s.length()-2); + multiplier = 1; + } else { + multiplier = 1000; + } + try { + pollInterval = Integer.parseInt(s) * multiplier; + } catch (NumberFormatException e) { + log.error("Invalid poll interval : " + param.getValue() + ", default to : " + + (BaseConstants.DEFAULT_POLL_INTERVAL / 1000) + "sec", e); + } + } + return pollInterval; + } + + @Override + protected void startListeningForService(AxisService service) throws AxisFault { + T entry = createPollTableEntry(service); + if (entry == null) { + throw new AxisFault("The service has no configuration for the transport"); + } + entry.setService(service); + entry.setPollInterval(getPollInterval(service)); + schedulePoll(entry); + pollTable.add(entry); + } + + /** + * Create a poll table entry based on the provided parameters. + * If no relevant parameters are found, the implementation should + * return null. An exception should only be thrown if there is an + * error or inconsistency in the parameters. + * + * @param params The source of the parameters to construct the + * poll table entry. If the parameters were defined on + * a service, this will be an {@link AxisService} + * instance. + * @return + */ + protected abstract T createPollTableEntry(ParameterInclude params) throws AxisFault; + + /** + * Get the EPR for the given service + * + * @param serviceName service name + * @param ip ignored + * @return the EPR for the service + * @throws AxisFault not used + */ + public EndpointReference[] getEPRsForService(String serviceName, String ip) throws AxisFault { + for (T entry : pollTable) { + AxisService service = entry.getService(); + if (service != null) { + String candidateName = service.getName(); + if (candidateName.equals(serviceName) || + serviceName.startsWith(candidateName + ".")) { + return new EndpointReference[]{ entry.getEndpointReference() }; + } + } + } + return null; + } + + @Override + protected void stopListeningForService(AxisService service) { + for (T entry : pollTable) { + if (service == entry.getService()) { + cancelPoll(entry); + break; + } + } + } + + // -- jmx/management methods-- + /** + * Pause the listener - Stop accepting/processing new messages, but continues processing existing + * messages until they complete. This helps bring an instance into a maintenence mode + * @throws org.apache.axis2.AxisFault on error + */ + public void pause() throws AxisFault { + if (state != BaseConstants.STARTED) return; + state = BaseConstants.PAUSED; + log.info("Listener paused"); + } + + /** + * Resume the lister - Brings the lister into active mode back from a paused state + * @throws AxisFault on error + */ + public void resume() throws AxisFault { + if (state != BaseConstants.PAUSED) return; + state = BaseConstants.STARTED; + log.info("Listener resumed"); + } + + /** + * Stop processing new messages, and wait the specified maximum time for in-flight + * requests to complete before a controlled shutdown for maintenence + * + * @param millis a number of milliseconds to wait until pending requests are allowed to complete + * @throws AxisFault on error + */ + public void maintenenceShutdown(long millis) throws AxisFault { + if (state != BaseConstants.STARTED) return; + stop(); + state = BaseConstants.STOPPED; + log.info("Listener shutdown"); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/AbstractTransportListener.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/AbstractTransportListener.java new file mode 100644 index 0000000000..440e09dc84 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/AbstractTransportListener.java @@ -0,0 +1,550 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.transport.base; + +import java.lang.management.ManagementFactory; +import java.util.ArrayList; +import java.util.Map; +import java.util.Set; + +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import org.apache.axiom.om.util.UUIDGenerator; +import org.apache.axis2.AxisFault; +import org.apache.axis2.addressing.EndpointReference; +import org.apache.axis2.context.ConfigurationContext; +import org.apache.axis2.context.MessageContext; +import org.apache.axis2.context.SessionContext; +import org.apache.axis2.description.AxisService; +import org.apache.axis2.description.TransportInDescription; +import org.apache.axis2.description.TransportOutDescription; +import org.apache.axis2.engine.AxisEngine; +import org.apache.axis2.transport.TransportListener; +import org.apache.axis2.util.MessageContextBuilder; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.threads.WorkerPool; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.threads.WorkerPoolFactory; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.tracker.AxisServiceFilter; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.tracker.AxisServiceTracker; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.tracker.AxisServiceTrackerListener; + +public abstract class AbstractTransportListener implements TransportListener { + + /** the reference to the actual commons logger to be used for log messages */ + protected Log log = null; + + /** the axis2 configuration context */ + protected ConfigurationContext cfgCtx = null; + + /** transport in description */ + private TransportInDescription transportIn = null; + /** transport out description */ + private TransportOutDescription transportOut = null; + /** state of the listener */ + protected int state = BaseConstants.STOPPED; + /** is this transport non-blocking? */ + protected boolean isNonBlocking = false; + /** + * Service tracker used to invoke {@link #internalStartListeningForService(AxisService)} + * and {@link #internalStopListeningForService(AxisService)}. */ + private AxisServiceTracker serviceTracker; + + /** the thread pool to execute actual poll invocations */ + protected WorkerPool workerPool = null; + /** use the thread pool available in the axis2 configuration context */ + protected boolean useAxis2ThreadPool = false; + /** JMX support */ + private TransportMBeanSupport mbeanSupport; + /** Metrics collector for this transport */ + protected MetricsCollector metrics = new MetricsCollector(); + + /** + * A constructor that makes subclasses pick up the correct logger + */ + protected AbstractTransportListener() { + log = LogFactory.getLog(this.getClass()); + } + + /** + * Initialize the generic transport. Sets up the transport and the thread pool to be used + * for message processing. Also creates an AxisObserver that gets notified of service + * life cycle events for the transport to act on + * @param cfgCtx the axis configuration context + * @param transportIn the transport-in description + * @throws AxisFault on error + */ + public void init(ConfigurationContext cfgCtx, TransportInDescription transportIn) + throws AxisFault { + + this.cfgCtx = cfgCtx; + this.transportIn = transportIn; + this.transportOut = cfgCtx.getAxisConfiguration().getTransportOut(getTransportName()); + + if (useAxis2ThreadPool) { + //this.workerPool = cfgCtx.getThreadPool(); not yet implemented + throw new AxisFault("Unsupported thread pool for task execution - Axis2 thread pool"); + } else { + this.workerPool = WorkerPoolFactory.getWorkerPool( + 10, 20, 5, -1, getTransportName() + "Server Worker thread group", getTransportName() + "-Worker"); + } + + // register to receive updates on services for lifetime management + serviceTracker = new AxisServiceTracker( + cfgCtx.getAxisConfiguration(), + new AxisServiceFilter() { + public boolean matches(AxisService service) { + return !service.getName().startsWith("__") // these are "private" services + && BaseUtils.isUsingTransport(service, getTransportName()); + } + }, + new AxisServiceTrackerListener() { + public void serviceAdded(AxisService service) { + internalStartListeningForService(service); + } + + public void serviceRemoved(AxisService service) { + internalStopListeningForService(service); + } + }); + + // register with JMX + mbeanSupport = new TransportMBeanSupport(this, getTransportName()); + mbeanSupport.register(); + } + + public void destroy() { + try { + if (state == BaseConstants.STARTED) { + try { + stop(); + } catch (AxisFault ignore) { + log.warn("Error stopping the transport : " + getTransportName()); + } + } + } finally { + state = BaseConstants.STOPPED; + mbeanSupport.unregister(); + } + try { + workerPool.shutdown(10000); + } catch (InterruptedException ex) { + log.warn("Thread interrupted while waiting for worker pool to shut down"); + } + } + + public void stop() throws AxisFault { + if (state == BaseConstants.STARTED) { + state = BaseConstants.STOPPED; + // cancel receipt of service lifecycle events + log.info(getTransportName().toUpperCase() + " Listener Shutdown"); + serviceTracker.stop(); + } + } + + public void start() throws AxisFault { + if (state != BaseConstants.STARTED) { + state = BaseConstants.STARTED; + // register to receive updates on services for lifetime management + // cfgCtx.getAxisConfiguration().addObservers(axisObserver); + log.info(getTransportName().toUpperCase() + " Listener started"); + // iterate through deployed services and start + serviceTracker.start(); + } + } + + public EndpointReference[] getEPRsForService(String serviceName, String ip) throws AxisFault { + return getEPRsForService(serviceName); + } + + protected EndpointReference[] getEPRsForService(String serviceName) { + return null; + } + + public void disableTransportForService(AxisService service) { + + log.warn("Disabling the " + getTransportName() + " transport for the service " + + service.getName() + ", because it is not configured properly for the service"); + + if (service.isEnableAllTransports()) { + ArrayList<String> exposedTransports = new ArrayList<String>(); + for(Object obj: cfgCtx.getAxisConfiguration().getTransportsIn().values()) { + String transportName = ((TransportInDescription) obj).getName(); + if (!transportName.equals(getTransportName())) { + exposedTransports.add(transportName); + } + } + service.setEnableAllTransports(false); + service.setExposedTransports(exposedTransports); + } else { + service.removeExposedTransport(getTransportName()); + } + } + + void internalStartListeningForService(AxisService service) { + String serviceName = service.getName(); + try { + startListeningForService(service); + } catch (AxisFault ex) { + String transportName = getTransportName().toUpperCase(); + String msg = "Unable to configure the service " + serviceName + " for the " + + transportName + " transport: " + ex.getMessage() + ". " + + "This service is being marked as faulty and will not be available over the " + + transportName + " transport."; + // Only log the message at level WARN and log the full stack trace at level DEBUG. + // TODO: We should have a way to distinguish a missing configuration + // from an error. This may be addressed when implementing the enhancement + // described in point 3 of http://markmail.org/message/umhenrurlrekk5jh + log.warn(msg); + log.debug("Disabling service " + serviceName + " for the " + transportName + + "transport", ex); + BaseUtils.markServiceAsFaulty(serviceName, msg, service.getAxisConfiguration()); + disableTransportForService(service); + return; + } catch (Throwable ex) { + String msg = "Unexpected error when configuring service " + serviceName + + " for the " + getTransportName().toUpperCase() + " transport. It will be" + + " disabled for this transport and marked as faulty."; + log.error(msg, ex); + BaseUtils.markServiceAsFaulty(serviceName, msg, service.getAxisConfiguration()); + disableTransportForService(service); + return; + } + registerMBean(new TransportListenerEndpointView(this, serviceName), + getEndpointMBeanName(serviceName)); + } + + void internalStopListeningForService(AxisService service) { + unregisterMBean(getEndpointMBeanName(service.getName())); + stopListeningForService(service); + } + + protected abstract void startListeningForService(AxisService service) throws AxisFault; + + protected abstract void stopListeningForService(AxisService service); + + /** + * This is a deprecated method in Axis2 and this default implementation returns the first + * result from the getEPRsForService() method + */ + public EndpointReference getEPRForService(String serviceName, String ip) throws AxisFault { + return getEPRsForService(serviceName, ip)[0]; + } + + public SessionContext getSessionContext(MessageContext messageContext) { + return null; + } + + /** + * Create a new axis MessageContext for an incoming message through this transport + * @return the newly created message context + */ + public MessageContext createMessageContext() { + MessageContext msgCtx = new MessageContext(); + msgCtx.setConfigurationContext(cfgCtx); + + msgCtx.setIncomingTransportName(getTransportName()); + msgCtx.setTransportOut(transportOut); + msgCtx.setTransportIn(transportIn); + msgCtx.setServerSide(true); + msgCtx.setMessageID(UUIDGenerator.getUUID()); + + // There is a discrepency in what I thought, Axis2 spawns a nes threads to + // send a message is this is TRUE - and I want it to be the other way + msgCtx.setProperty(MessageContext.TRANSPORT_NON_BLOCKING, Boolean.valueOf(!isNonBlocking)); + + // are these relevant? + //msgCtx.setServiceGroupContextId(UUIDGenerator.getUUID()); + // this is required to support Sandesha 2 + //msgContext.setProperty(RequestResponseTransport.TRANSPORT_CONTROL, + // new HttpCoreRequestResponseTransport(msgContext)); + + return msgCtx; + } + + /** + * Process a new incoming message through the axis engine + * @param msgCtx the axis MessageContext + * @param trpHeaders the map containing transport level message headers + * @param soapAction the optional soap action or null + * @param contentType the optional content-type for the message + */ + public void handleIncomingMessage( + MessageContext msgCtx, Map trpHeaders, + String soapAction, String contentType) throws AxisFault { + + // set the soapaction if one is available via a transport header + if (soapAction != null) { + msgCtx.setSoapAction(soapAction); + } + + // set the transport headers to the message context + msgCtx.setProperty(MessageContext.TRANSPORT_HEADERS, trpHeaders); + + // send the message context through the axis engine + try { + // check if an Axis2 callback has been registered for this message + Map callBackMap = (Map) msgCtx.getConfigurationContext(). + getProperty(BaseConstants.CALLBACK_TABLE); + // FIXME: transport headers are protocol specific; the correlation ID should be + // passed as argument to this method + Object replyToMessageID = trpHeaders.get(BaseConstants.HEADER_IN_REPLY_TO); + // if there is a callback registerd with this replyto ID then this has to + // be handled as a synchronous incoming message, via the + if (replyToMessageID != null && callBackMap != null && + callBackMap.get(replyToMessageID) != null) { + + SynchronousCallback synchronousCallback = + (SynchronousCallback) callBackMap.get(replyToMessageID); + synchronousCallback.setInMessageContext(msgCtx); + callBackMap.remove(replyToMessageID); + } else { + AxisEngine.receive(msgCtx); + } + + } catch (AxisFault e) { + if (log.isDebugEnabled()) { + log.debug("Error receiving message", e); + } + if (msgCtx.isServerSide()) { + AxisEngine.sendFault(MessageContextBuilder.createFaultMessageContext(msgCtx, e)); + } + } + } + + protected void handleException(String msg, Exception e) throws AxisFault { + log.error(msg, e); + throw new AxisFault(msg, e); + } + + protected void logException(String msg, Exception e) { + log.error(msg, e); + } + + public String getTransportName() { + return transportIn.getName(); + } + + public MetricsCollector getMetricsCollector() { + return metrics; + } + + // -- jmx/management methods-- + /** + * Pause the listener - Stop accepting/processing new messages, but continues processing existing + * messages until they complete. This helps bring an instance into a maintenence mode + * @throws AxisFault on error + */ + public void pause() throws AxisFault {} + /** + * Resume the lister - Brings the lister into active mode back from a paused state + * @throws AxisFault on error + */ + public void resume() throws AxisFault {} + + /** + * Stop processing new messages, and wait the specified maximum time for in-flight + * requests to complete before a controlled shutdown for maintenence + * + * @param millis a number of milliseconds to wait until pending requests are allowed to complete + * @throws AxisFault on error + */ + public void maintenenceShutdown(long millis) throws AxisFault {} + + /** + * Returns the number of active threads processing messages + * @return number of active threads processing messages + */ + public int getActiveThreadCount() { + return workerPool.getActiveCount(); + } + + /** + * Return the number of requests queued in the thread pool + * @return queue size + */ + public int getQueueSize() { + return workerPool.getQueueSize(); + } + + public long getMessagesReceived() { + if (metrics != null) { + return metrics.getMessagesReceived(); + } + return -1; + } + + public long getFaultsReceiving() { + if (metrics != null) { + return metrics.getFaultsReceiving(); + } + return -1; + } + + public long getBytesReceived() { + if (metrics != null) { + return metrics.getBytesReceived(); + } + return -1; + } + + public long getMessagesSent() { + if (metrics != null) { + return metrics.getMessagesSent(); + } + return -1; + } + + public long getFaultsSending() { + if (metrics != null) { + return metrics.getFaultsSending(); + } + return -1; + } + + public long getBytesSent() { + if (metrics != null) { + return metrics.getBytesSent(); + } + return -1; + } + + public long getTimeoutsReceiving() { + if (metrics != null) { + return metrics.getTimeoutsReceiving(); + } + return -1; + } + + public long getTimeoutsSending() { + if (metrics != null) { + return metrics.getTimeoutsSending(); + } + return -1; + } + + public long getMinSizeReceived() { + if (metrics != null) { + return metrics.getMinSizeReceived(); + } + return -1; + } + + public long getMaxSizeReceived() { + if (metrics != null) { + return metrics.getMaxSizeReceived(); + } + return -1; + } + + public double getAvgSizeReceived() { + if (metrics != null) { + return metrics.getAvgSizeReceived(); + } + return -1; + } + + public long getMinSizeSent() { + if (metrics != null) { + return metrics.getMinSizeSent(); + } + return -1; + } + + public long getMaxSizeSent() { + if (metrics != null) { + return metrics.getMaxSizeSent(); + } + return -1; + } + + public double getAvgSizeSent() { + if (metrics != null) { + return metrics.getAvgSizeSent(); + } + return -1; + } + + public Map getResponseCodeTable() { + if (metrics != null) { + return metrics.getResponseCodeTable(); + } + return null; + } + + public void resetStatistics() { + if (metrics != null) { + metrics.reset(); + } + } + + public long getLastResetTime() { + if (metrics != null) { + return metrics.getLastResetTime(); + } + return -1; + } + + public long getMetricsWindow() { + if (metrics != null) { + return System.currentTimeMillis() - metrics.getLastResetTime(); + } + return -1; + } + + private String getEndpointMBeanName(String serviceName) { + return mbeanSupport.getMBeanName() + ",Group=Services,Service=" + serviceName; + } + + /** + * Utility method to allow transports to register MBeans + * @param mbeanInstance bean instance + * @param objectName name + */ + private void registerMBean(Object mbeanInstance, String objectName) { + try { + MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + ObjectName name = new ObjectName(objectName); + Set set = mbs.queryNames(name, null); + if (set != null && set.isEmpty()) { + mbs.registerMBean(mbeanInstance, name); + } else { + mbs.unregisterMBean(name); + mbs.registerMBean(mbeanInstance, name); + } + } catch (Exception e) { + log.warn("Error registering a MBean with objectname ' " + objectName + + " ' for JMX management", e); + } + } + + private void unregisterMBean(String objectName) { + try { + MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + ObjectName objName = new ObjectName(objectName); + if (mbs.isRegistered(objName)) { + mbs.unregisterMBean(objName); + } + } catch (Exception e) { + log.warn("Error un-registering a MBean with objectname ' " + objectName + + " ' for JMX management", e); + } + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/AbstractTransportSender.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/AbstractTransportSender.java new file mode 100644 index 0000000000..c2c84c3eb2 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/AbstractTransportSender.java @@ -0,0 +1,419 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.transport.base; + +import java.util.Map; +import java.util.Set; + +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import org.apache.axiom.om.util.UUIDGenerator; +import org.apache.axis2.AxisFault; +import org.apache.axis2.Constants; +import org.apache.axis2.context.ConfigurationContext; +import org.apache.axis2.context.MessageContext; +import org.apache.axis2.description.TransportInDescription; +import org.apache.axis2.description.TransportOutDescription; +import org.apache.axis2.description.WSDL2Constants; +import org.apache.axis2.engine.AxisEngine; +import org.apache.axis2.handlers.AbstractHandler; +import org.apache.axis2.transport.OutTransportInfo; +import org.apache.axis2.transport.TransportSender; +import org.apache.axis2.util.MessageContextBuilder; +import org.apache.axis2.wsdl.WSDLConstants; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public abstract class AbstractTransportSender extends AbstractHandler implements TransportSender { + + /** the reference to the actual commons logger to be used for log messages */ + protected Log log = null; + + /** the axis2 configuration context */ + protected ConfigurationContext cfgCtx = null; + /** transport in description */ + private TransportInDescription transportIn = null; + /** transport out description */ + private TransportOutDescription transportOut = null; + /** JMX support */ + private TransportMBeanSupport mbeanSupport; + /** Metrics collector for the sender */ + protected MetricsCollector metrics = new MetricsCollector(); + /** state of the listener */ + private int state = BaseConstants.STOPPED; + + /** + * A constructor that makes subclasses pick up the correct logger + */ + protected AbstractTransportSender() { + log = LogFactory.getLog(this.getClass()); + } + + /** + * Initialize the generic transport sender. + * + * @param cfgCtx the axis configuration context + * @param transportOut the transport-out description + * @throws AxisFault on error + */ + public void init(ConfigurationContext cfgCtx, TransportOutDescription transportOut) + throws AxisFault { + this.cfgCtx = cfgCtx; + this.transportOut = transportOut; + this.transportIn = cfgCtx.getAxisConfiguration().getTransportIn(getTransportName()); + this.state = BaseConstants.STARTED; + + // register with JMX + mbeanSupport = new TransportMBeanSupport(this, getTransportName()); + mbeanSupport.register(); + log.info(getTransportName().toUpperCase() + " Sender started"); + } + + public void stop() { + if (state != BaseConstants.STARTED) return; + state = BaseConstants.STOPPED; + mbeanSupport.unregister(); + log.info(getTransportName().toUpperCase() + " Sender Shutdown"); + } + + public void cleanup(MessageContext msgContext) throws AxisFault {} + + public abstract void sendMessage(MessageContext msgCtx, String targetEPR, + OutTransportInfo outTransportInfo) throws AxisFault; + + public InvocationResponse invoke(MessageContext msgContext) throws AxisFault { + + // is there a transport url which may be different to the WS-A To but has higher precedence + String targetAddress = (String) msgContext.getProperty( + Constants.Configuration.TRANSPORT_URL); + + if (targetAddress != null) { + sendMessage(msgContext, targetAddress, null); + } else if (msgContext.getTo() != null && !msgContext.getTo().hasAnonymousAddress()) { + targetAddress = msgContext.getTo().getAddress(); + + if (!msgContext.getTo().hasNoneAddress()) { + sendMessage(msgContext, targetAddress, null); + } else { + //Don't send the message. + return InvocationResponse.CONTINUE; + } + } else if (msgContext.isServerSide()) { + // get the out transport info for server side when target EPR is unknown + sendMessage(msgContext, null, + (OutTransportInfo) msgContext.getProperty(Constants.OUT_TRANSPORT_INFO)); + } + + return InvocationResponse.CONTINUE; + } + + /** + * Process a new incoming message (Response) through the axis engine + * @param msgCtx the axis MessageContext + * @param trpHeaders the map containing transport level message headers + * @param soapAction the optional soap action or null + * @param contentType the optional content-type for the message + */ + public void handleIncomingMessage( + MessageContext msgCtx, Map trpHeaders, + String soapAction, String contentType) { + + + // set the soapaction if one is available via a transport header + if (soapAction != null) { + msgCtx.setSoapAction(soapAction); + } + + // set the transport headers to the message context + msgCtx.setProperty(MessageContext.TRANSPORT_HEADERS, trpHeaders); + + // send the message context through the axis engine + try { + try { + AxisEngine.receive(msgCtx); + } catch (AxisFault e) { + if (log.isDebugEnabled()) { + log.debug("Error receiving message", e); + } + if (msgCtx.isServerSide()) { + AxisEngine.sendFault(MessageContextBuilder.createFaultMessageContext(msgCtx, e)); + } + } + } catch (AxisFault axisFault) { + logException("Error processing response message", axisFault); + } + } + + /** + * Create a new axis MessageContext for an incoming response message + * through this transport, for the given outgoing message + * + * @param outMsgCtx the outgoing message + * @return the newly created message context + */ + public MessageContext createResponseMessageContext(MessageContext outMsgCtx) { + + MessageContext responseMsgCtx = null; + try { + responseMsgCtx = outMsgCtx.getOperationContext(). + getMessageContext(WSDL2Constants.MESSAGE_LABEL_IN); + } catch (AxisFault af) { + log.error("Error getting IN message context from the operation context", af); + } + + if (responseMsgCtx == null) { + responseMsgCtx = new MessageContext(); + responseMsgCtx.setOperationContext(outMsgCtx.getOperationContext()); + } + + responseMsgCtx.setIncomingTransportName(getTransportName()); + responseMsgCtx.setTransportOut(transportOut); + responseMsgCtx.setTransportIn(transportIn); + + responseMsgCtx.setMessageID(UUIDGenerator.getUUID()); + + responseMsgCtx.setDoingREST(outMsgCtx.isDoingREST()); + responseMsgCtx.setProperty( + MessageContext.TRANSPORT_IN, outMsgCtx.getProperty(MessageContext.TRANSPORT_IN)); + responseMsgCtx.setAxisMessage(outMsgCtx.getOperationContext().getAxisOperation(). + getMessage(WSDLConstants.MESSAGE_LABEL_IN_VALUE)); + responseMsgCtx.setTo(null); + //msgCtx.setProperty(MessageContext.TRANSPORT_NON_BLOCKING, isNonBlocking); + + + // are these relevant? + //msgCtx.setServiceGroupContextId(UUIDGenerator.getUUID()); + // this is required to support Sandesha 2 + //msgContext.setProperty(RequestResponseTransport.TRANSPORT_CONTROL, + // new HttpCoreRequestResponseTransport(msgContext)); + + return responseMsgCtx; + } + + /** + * Should the transport sender wait for a synchronous response to be received? + * @param msgCtx the outgoing message context + * @return true if a sync response is expected + */ + protected boolean waitForSynchronousResponse(MessageContext msgCtx) { + return + msgCtx.getOperationContext() != null && + WSDL2Constants.MEP_URI_OUT_IN.equals( + msgCtx.getOperationContext().getAxisOperation().getMessageExchangePattern()); + } + + public String getTransportName() { + return transportOut.getName(); + } + + protected void handleException(String msg, Exception e) throws AxisFault { + log.error(msg, e); + throw new AxisFault(msg, e); + } + + protected void handleException(String msg) throws AxisFault { + log.error(msg); + throw new AxisFault(msg); + } + + protected void logException(String msg, Exception e) { + log.error(msg, e); + } + + //--- jmx/management methods --- + public void pause() throws AxisFault { + if (state != BaseConstants.STARTED) return; + state = BaseConstants.PAUSED; + log.info("Sender paused"); + } + + public void resume() throws AxisFault { + if (state != BaseConstants.PAUSED) return; + state = BaseConstants.STARTED; + log.info("Sender resumed"); + } + + public void maintenenceShutdown(long millis) throws AxisFault { + if (state != BaseConstants.STARTED) return; + long start = System.currentTimeMillis(); + stop(); + state = BaseConstants.STOPPED; + log.info("Sender shutdown in : " + (System.currentTimeMillis() - start) / 1000 + "s"); + } + + /** + * Returns the number of active threads processing messages + * @return number of active threads processing messages + */ + public int getActiveThreadCount() { + return 0; + } + + /** + * Return the number of requests queued in the thread pool + * @return queue size + */ + public int getQueueSize() { + return 0; + } + + // -- jmx/management methods-- + public long getMessagesReceived() { + if (metrics != null) { + return metrics.getMessagesReceived(); + } + return -1; + } + + public long getFaultsReceiving() { + if (metrics != null) { + return metrics.getFaultsReceiving(); + } + return -1; + } + + public long getBytesReceived() { + if (metrics != null) { + return metrics.getBytesReceived(); + } + return -1; + } + + public long getMessagesSent() { + if (metrics != null) { + return metrics.getMessagesSent(); + } + return -1; + } + + public long getFaultsSending() { + if (metrics != null) { + return metrics.getFaultsSending(); + } + return -1; + } + + public long getBytesSent() { + if (metrics != null) { + return metrics.getBytesSent(); + } + return -1; + } + + public long getTimeoutsReceiving() { + if (metrics != null) { + return metrics.getTimeoutsReceiving(); + } + return -1; + } + + public long getTimeoutsSending() { + if (metrics != null) { + return metrics.getTimeoutsSending(); + } + return -1; + } + + public long getMinSizeReceived() { + if (metrics != null) { + return metrics.getMinSizeReceived(); + } + return -1; + } + + public long getMaxSizeReceived() { + if (metrics != null) { + return metrics.getMaxSizeReceived(); + } + return -1; + } + + public double getAvgSizeReceived() { + if (metrics != null) { + return metrics.getAvgSizeReceived(); + } + return -1; + } + + public long getMinSizeSent() { + if (metrics != null) { + return metrics.getMinSizeSent(); + } + return -1; + } + + public long getMaxSizeSent() { + if (metrics != null) { + return metrics.getMaxSizeSent(); + } + return -1; + } + + public double getAvgSizeSent() { + if (metrics != null) { + return metrics.getAvgSizeSent(); + } + return -1; + } + + public Map getResponseCodeTable() { + if (metrics != null) { + return metrics.getResponseCodeTable(); + } + return null; + } + + public void resetStatistics() { + if (metrics != null) { + metrics.reset(); + } + } + + public long getLastResetTime() { + if (metrics != null) { + return metrics.getLastResetTime(); + } + return -1; + } + + public long getMetricsWindow() { + if (metrics != null) { + return System.currentTimeMillis() - metrics.getLastResetTime(); + } + return -1; + } + + private void registerMBean(MBeanServer mbs, Object mbeanInstance, String objectName) { + try { + ObjectName name = new ObjectName(objectName); + Set set = mbs.queryNames(name, null); + if (set != null && set.isEmpty()) { + mbs.registerMBean(mbeanInstance, name); + } else { + mbs.unregisterMBean(name); + mbs.registerMBean(mbeanInstance, name); + } + } catch (Exception e) { + log.warn("Error registering a MBean with objectname ' " + objectName + + " ' for JMX management", e); + } + } + +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/BaseConstants.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/BaseConstants.java new file mode 100644 index 0000000000..5edf76b898 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/BaseConstants.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.transport.base; + + +import javax.xml.namespace.QName; + +public class BaseConstants { + // -- status of a transport -- + public final static int STOPPED = 0; + public final static int STARTED = 1; + public final static int PAUSED = 2; + + /** + * A message property specifying the SOAP Action + */ + public static final String SOAPACTION = "SOAPAction"; + /** + * A message property specifying the content type + */ + public static final String CONTENT_TYPE = "Content-Type"; + /** + * A message context property indicating "TRUE", if a transport or the message builder + * has information that the current message is a fault (e.g. SOAP faults, non-HTTP 2xx, etc) + */ + public static final String FAULT_MESSAGE = "FAULT_MESSAGE"; + /** + * content type identifier for multipart / MTOM messages + */ + public static final String MULTIPART_RELATED = "multipart/related"; + /** + * character set marker to identify charset from a Content-Type string + */ + public static final String CHARSET_PARAM = "; charset="; + /** + * The property specifying an optional message level metrics collector + */ + public static final String METRICS_COLLECTOR = "METRICS_COLLECTOR"; + + //------------------------------------ defaults ------------------------------------ + /** + * The default operation name to be used for non SOAP/XML messages + * if the operation cannot be determined + */ + public static final QName DEFAULT_OPERATION = new QName("urn:mediate"); + /** + * The name of the element which wraps binary content into a SOAP envelope + */ + + // This has to match org.apache.synapse.util.PayloadHelper + // at some future point this can be merged into Axiom as a common base + public final static String AXIOMPAYLOADNS = "http://ws.apache.org/commons/ns/payload"; + + + public static final QName DEFAULT_BINARY_WRAPPER = + new QName(AXIOMPAYLOADNS, "binary"); + /** + * The name of the element which wraps plain text content into a SOAP envelope + */ + public static final QName DEFAULT_TEXT_WRAPPER = + new QName(AXIOMPAYLOADNS, "text"); + + //-------------------------- services.xml parameters -------------------------------- + /** + * The Parameter name indicating the operation to dispatch non SOAP/XML messages + */ + public static final String OPERATION_PARAM = "Operation"; + /** + * The Parameter name indicating the wrapper element for non SOAP/XML messages + */ + public static final String WRAPPER_PARAM = "Wrapper"; + /** + * the parameter in the services.xml that specifies the poll interval for a service + */ + public static final String TRANSPORT_POLL_INTERVAL = "transport.PollInterval"; + /** + * Could polling take place in parallel, i.e. starting at fixed intervals? + */ + public static final String TRANSPORT_POLL_IN_PARALLEL = "transport.ConcurrentPollingAllowed"; + /** + * The default poll interval in milliseconds. + */ + public static final int DEFAULT_POLL_INTERVAL = 5 * 60 * 1000; // 5 mins by default + + public static final String CALLBACK_TABLE = "callbackTable"; + public static final String HEADER_IN_REPLY_TO = "In-Reply-To"; + + // this is an property required by axis2 + // FIXME: where is this required in Axis2? + public final static String MAIL_CONTENT_TYPE = "mail.contenttype"; + + /** Service transaction level - non-transactional */ + public static final int TRANSACTION_NONE = 0; + /** Service transaction level - use non-JTA (i.e. local) transactions */ + public static final int TRANSACTION_LOCAL = 1; + /** Service transaction level - use JTA transactions */ + public static final int TRANSACTION_JTA = 2; + /** Service transaction level - non-transactional */ + public static final String STR_TRANSACTION_NONE = "none"; + /** Service transaction level - use non-JTA (i.e. local) transactions */ + public static final String STR_TRANSACTION_LOCAL = "local"; + /** Service transaction level - use JTA transactions */ + public static final String STR_TRANSACTION_JTA = "jta"; + + /** The Parameter name indicating the transactionality of a service */ + public static final String PARAM_TRANSACTIONALITY = "transport.Transactionality"; + /** Parameter name indicating the JNDI name to get a UserTransaction from JNDI */ + public static final String PARAM_USER_TXN_JNDI_NAME = "transport.UserTxnJNDIName"; + /** Parameter that indicates if a UserTransaction reference could be cached - default yes */ + public static final String PARAM_CACHE_USER_TXN = "transport.CacheUserTxn"; + + /** The UserTransaction associated with this message */ + public static final String USER_TRANSACTION = "UserTransaction"; + /** A message level property indicating a request to rollback the transaction associated with the message */ + public static final String SET_ROLLBACK_ONLY = "SET_ROLLBACK_ONLY"; + /** A message level property indicating a commit is required after the next immidiate send over a transport */ + public static final String JTA_COMMIT_AFTER_SEND = "JTA_COMMIT_AFTER_SEND"; +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/BaseTransportException.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/BaseTransportException.java new file mode 100644 index 0000000000..b4d4243ad2 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/BaseTransportException.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.transport.base; + +public class BaseTransportException extends RuntimeException { + + BaseTransportException() { + super(); + } + + public BaseTransportException(String msg) { + super(msg); + } + + public BaseTransportException(String msg, Exception e) { + super(msg, e); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/BaseUtils.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/BaseUtils.java new file mode 100644 index 0000000000..008d1b5f28 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/BaseUtils.java @@ -0,0 +1,229 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.transport.base; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Hashtable; +import java.util.List; +import java.util.StringTokenizer; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMOutputFormat; +import org.apache.axiom.om.impl.builder.StAXBuilder; +import org.apache.axiom.om.util.StAXUtils; +import org.apache.axiom.soap.SOAPEnvelope; +import org.apache.axiom.soap.impl.builder.StAXSOAPModelBuilder; +import org.apache.axis2.AxisFault; +import org.apache.axis2.Constants; +import org.apache.axis2.context.MessageContext; +import org.apache.axis2.description.AxisService; +import org.apache.axis2.engine.AxisConfiguration; +import org.apache.axis2.transport.MessageFormatter; +import org.apache.axis2.transport.TransportUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.tuscany.sca.binding.ws.axis2.format.BinaryFormatter; +import org.apache.tuscany.sca.binding.ws.axis2.format.PlainTextFormatter; + +public class BaseUtils { + + private static final Log log = LogFactory.getLog(BaseUtils.class); + + /** + * Return a QName from the String passed in of the form {ns}element + * @param obj a QName or a String containing a QName in {ns}element form + * @return a corresponding QName object + */ + public static QName getQNameFromString(Object obj) { + String value; + if (obj instanceof QName) { + return (QName) obj; + } else { + value = obj.toString(); + } + int open = value.indexOf('{'); + int close = value.indexOf('}'); + if (close > open && open > -1 && value.length() > close) { + return new QName(value.substring(open+1, close-open), value.substring(close+1)); + } else { + return new QName(value); + } + } + + /** + * Marks the given service as faulty with the given comment + * + * @param serviceName service name + * @param msg comment for being faulty + * @param axisCfg configuration context + */ + public static void markServiceAsFaulty(String serviceName, String msg, + AxisConfiguration axisCfg) { + if (serviceName != null) { + try { + AxisService service = axisCfg.getService(serviceName); + axisCfg.getFaultyServices().put(service.getName(), msg); + + } catch (AxisFault axisFault) { + log.warn("Error marking service : " + serviceName + " as faulty", axisFault); + } + } + } + + /** + * Create a SOAP envelope using SOAP 1.1 or 1.2 depending on the namespace + * @param in InputStream for the payload + * @param namespace the SOAP namespace + * @return the SOAP envelope for the correct version + * @throws javax.xml.stream.XMLStreamException on error + */ + public static SOAPEnvelope getEnvelope(InputStream in, String namespace) throws XMLStreamException { + + try { + in.reset(); + } catch (IOException ignore) {} + XMLStreamReader xmlreader = + StAXUtils.createXMLStreamReader(in, MessageContext.DEFAULT_CHAR_SET_ENCODING); + StAXBuilder builder = new StAXSOAPModelBuilder(xmlreader, namespace); + return (SOAPEnvelope) builder.getDocumentElement(); + } + + /** + * Get the OMOutput format for the given message + * @param msgContext the axis message context + * @return the OMOutput format to be used + */ + public static OMOutputFormat getOMOutputFormat(MessageContext msgContext) { + + OMOutputFormat format = new OMOutputFormat(); + msgContext.setDoingMTOM(TransportUtils.doWriteMTOM(msgContext)); + msgContext.setDoingSwA(TransportUtils.doWriteSwA(msgContext)); + msgContext.setDoingREST(TransportUtils.isDoingREST(msgContext)); + format.setSOAP11(msgContext.isSOAP11()); + format.setDoOptimize(msgContext.isDoingMTOM()); + format.setDoingSWA(msgContext.isDoingSwA()); + + format.setCharSetEncoding(TransportUtils.getCharSetEncoding(msgContext)); + Object mimeBoundaryProperty = msgContext.getProperty(Constants.Configuration.MIME_BOUNDARY); + if (mimeBoundaryProperty != null) { + format.setMimeBoundary((String) mimeBoundaryProperty); + } + return format; + } + + /** + * Get the MessageFormatter for the given message. + * @param msgContext the axis message context + * @return the MessageFormatter to be used + */ + public static MessageFormatter getMessageFormatter(MessageContext msgContext) { + // check the first element of the SOAP body, do we have content wrapped using the + // default wrapper elements for binary (BaseConstants.DEFAULT_BINARY_WRAPPER) or + // text (BaseConstants.DEFAULT_TEXT_WRAPPER) ? If so, select the appropriate + // message formatter directly ... + OMElement firstChild = msgContext.getEnvelope().getBody().getFirstElement(); + if (firstChild != null) { + if (BaseConstants.DEFAULT_BINARY_WRAPPER.equals(firstChild.getQName())) { + return new BinaryFormatter(); + } else if (BaseConstants.DEFAULT_TEXT_WRAPPER.equals(firstChild.getQName())) { + return new PlainTextFormatter(); + } + } + + // ... otherwise, let Axis choose the right message formatter: + try { + return TransportUtils.getMessageFormatter(msgContext); + } catch (AxisFault axisFault) { + throw new BaseTransportException("Unable to get the message formatter to use"); + } + } + + protected static void handleException(String s) { + log.error(s); + throw new BaseTransportException(s); + } + + protected static void handleException(String s, Exception e) { + log.error(s, e); + throw new BaseTransportException(s, e); + } + + /** + * Utility method to check if a string is null or empty + * @param str the string to check + * @return true if the string is null or empty + */ + public static boolean isBlank(String str) { + if (str == null || str.length() == 0) { + return true; + } + for (int i = 0; i < str.length(); i++) { + if (!Character.isWhitespace(str.charAt(i))) { + return false; + } + } + return true; + } + + public static boolean isUsingTransport(AxisService service, String transportName) { + boolean process = service.isEnableAllTransports(); + if (process) { + return true; + + } else { + List transports = service.getExposedTransports(); + for (Object transport : transports) { + if (transportName.equals(transport)) { + return true; + } + } + } + return false; + } + + /** + * Extract the properties from an endpoint reference. + * + * @param url an endpoint reference + * @return the extracted properties + */ + public static Hashtable<String,String> getEPRProperties(String url) { + Hashtable<String,String> h = new Hashtable<String,String>(); + int propPos = url.indexOf("?"); + if (propPos != -1) { + StringTokenizer st = new StringTokenizer(url.substring(propPos + 1), "&"); + while (st.hasMoreTokens()) { + String token = st.nextToken(); + int sep = token.indexOf("="); + if (sep != -1) { + h.put(token.substring(0, sep), token.substring(sep + 1)); + } else { + // ignore, what else can we do? + } + } + } + return h; + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/ManagementSupport.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/ManagementSupport.java new file mode 100644 index 0000000000..8b20b4a0b3 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/ManagementSupport.java @@ -0,0 +1,51 @@ +package org.apache.tuscany.sca.binding.ws.axis2.transport.base; + +import java.util.Map; + +import org.apache.axis2.AxisFault; + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ +public interface ManagementSupport { + public void pause() throws AxisFault; + public void resume() throws AxisFault; + void maintenenceShutdown(long millis) throws AxisFault; + public int getActiveThreadCount(); + public int getQueueSize(); + + public long getMessagesReceived(); + public long getFaultsReceiving(); + public long getTimeoutsReceiving(); + public long getMessagesSent(); + public long getFaultsSending(); + public long getTimeoutsSending(); + public long getBytesReceived(); + public long getBytesSent(); + public long getMinSizeReceived(); + public long getMaxSizeReceived(); + public double getAvgSizeReceived(); + public long getMinSizeSent(); + public long getMaxSizeSent(); + public double getAvgSizeSent(); + public Map getResponseCodeTable(); + + public void resetStatistics(); + public long getLastResetTime(); + public long getMetricsWindow(); +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/MessageLevelMetricsCollector.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/MessageLevelMetricsCollector.java new file mode 100644 index 0000000000..59dc9e9118 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/MessageLevelMetricsCollector.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.transport.base; + +public interface MessageLevelMetricsCollector { + + public void incrementMessagesReceived(); + + public void incrementFaultsReceiving(int errorCode); + + public void incrementTimeoutsReceiving(); + + public void incrementBytesReceived(long size); + + public void incrementMessagesSent(); + + public void incrementFaultsSending(int errorCode); + + public void incrementTimeoutsSending(); + + public void incrementBytesSent(long size); + + public void notifyReceivedMessageSize(long size); + + public void notifySentMessageSize(long size); + + public void reportReceivingFault(int errorCode); + + public void reportSendingFault(int errorCode); + + public void reportResponseCode(int respCode); +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/MetricsCollector.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/MetricsCollector.java new file mode 100644 index 0000000000..45dcde944c --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/MetricsCollector.java @@ -0,0 +1,315 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.transport.base; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.axis2.context.MessageContext; + +/** + * Collects metrics related to a transport that has metrics support enabled + */ +public class MetricsCollector { + + public static final int LEVEL_NONE = 0; + public static final int LEVEL_TRANSPORT = 1; + public static final int LEVEL_FULL = 2; + private static final Long ONE = (long) 1; + + /** By default, full metrics collection is enabled */ + private int level = LEVEL_FULL; + + private long messagesReceived; + private long faultsReceiving; + private long timeoutsReceiving; + private long bytesReceived; + private long minSizeReceived; + private long maxSizeReceived; + private double avgSizeReceived; + + private long messagesSent; + private long faultsSending; + private long timeoutsSending; + private long bytesSent; + private long minSizeSent; + private long maxSizeSent; + private double avgSizeSent; + + private final Map<Integer, Long> responseCodeTable = + Collections.synchronizedMap(new HashMap<Integer, Long>()); + + private long lastResetTime = System.currentTimeMillis(); + + public void reset() { + messagesReceived = 0; + faultsReceiving = 0; + timeoutsReceiving = 0; + bytesReceived = 0; + minSizeReceived = 0; + maxSizeReceived = 0; + avgSizeReceived = 0; + + messagesSent = 0; + faultsSending = 0; + timeoutsSending = 0; + bytesSent = 0; + minSizeSent = 0; + maxSizeSent = 0; + avgSizeSent = 0; + + responseCodeTable.clear(); + lastResetTime = System.currentTimeMillis(); + } + + public int getLevel() { + return level; + } + + public void setLevel(int level) { + this.level = level; + } + + public long getLastResetTime() { + return lastResetTime; + } + + public long getMessagesReceived() { + return messagesReceived; + } + + public long getFaultsReceiving() { + return faultsReceiving; + } + + public long getTimeoutsReceiving() { + return timeoutsReceiving; + } + + public long getBytesReceived() { + return bytesReceived; + } + + public long getMessagesSent() { + return messagesSent; + } + + public long getFaultsSending() { + return faultsSending; + } + + public long getTimeoutsSending() { + return timeoutsSending; + } + + public long getBytesSent() { + return bytesSent; + } + + public long getMinSizeReceived() { + return minSizeReceived; + } + + public long getMaxSizeReceived() { + return maxSizeReceived; + } + + public long getMinSizeSent() { + return minSizeSent; + } + + public long getMaxSizeSent() { + return maxSizeSent; + } + + public double getAvgSizeReceived() { + return avgSizeReceived; + } + + public double getAvgSizeSent() { + return avgSizeSent; + } + + public Map<Integer, Long> getResponseCodeTable() { + return responseCodeTable; + } + + public synchronized void incrementMessagesReceived() { + messagesReceived++; + } + + public synchronized void incrementFaultsReceiving() { + faultsReceiving++; + } + + public synchronized void incrementTimeoutsReceiving() { + timeoutsReceiving++; + } + + public synchronized void incrementBytesReceived(long size) { + bytesReceived += size; + } + + public synchronized void incrementMessagesSent() { + messagesSent++; + } + + public synchronized void incrementFaultsSending() { + faultsSending++; + } + + public synchronized void incrementTimeoutsSending() { + timeoutsSending++; + } + + public synchronized void incrementBytesSent(long size) { + bytesSent += size; + } + + public synchronized void notifyReceivedMessageSize(long size) { + if (minSizeReceived == 0 || size < minSizeReceived) { + minSizeReceived = size; + } + if (size > maxSizeReceived) { + maxSizeReceived = size; + } + avgSizeReceived = (avgSizeReceived == 0 ? size : (avgSizeReceived + size) / 2); + } + + public synchronized void notifySentMessageSize(long size) { + if (minSizeSent == 0 || size < minSizeSent) { + minSizeSent = size; + } + if (size > maxSizeSent) { + maxSizeSent = size; + } + avgSizeSent = (avgSizeSent == 0 ? size : (avgSizeSent + size) / 2); + } + + public void reportResponseCode(int respCode) { + synchronized(responseCodeTable) { + Object o = responseCodeTable.get(respCode); + if (o == null) { + responseCodeTable.put(respCode, ONE); + } else { + responseCodeTable.put(respCode, (Long) o + 1); + } + } + } + + // --- enhanced methods --- + private MessageLevelMetricsCollector getMsgLevelMetrics(MessageContext mc) { + if (mc != null && level == LEVEL_FULL) { + return (MessageLevelMetricsCollector) mc.getProperty(BaseConstants.METRICS_COLLECTOR); + } + return null; + } + + public void incrementMessagesReceived(MessageContext mc) { + incrementMessagesReceived(); + MessageLevelMetricsCollector m = getMsgLevelMetrics(mc); + if (m != null) { + m.incrementMessagesReceived(); + } + } + + public void incrementFaultsReceiving(int errorCode, MessageContext mc) { + incrementFaultsReceiving(); + MessageLevelMetricsCollector m = getMsgLevelMetrics(mc); + if (m != null) { + m.incrementFaultsReceiving(errorCode); + } + } + + public void incrementTimeoutsReceiving(MessageContext mc) { + incrementTimeoutsReceiving(); + MessageLevelMetricsCollector m = getMsgLevelMetrics(mc); + if (m != null) { + m.incrementTimeoutsReceiving(); + } + } + + public void incrementBytesReceived(MessageContext mc, long size) { + incrementBytesReceived(size); + MessageLevelMetricsCollector m = getMsgLevelMetrics(mc); + if (m != null) { + m.incrementBytesReceived(size); + } + } + + public void incrementMessagesSent(MessageContext mc) { + incrementMessagesSent(); + MessageLevelMetricsCollector m = getMsgLevelMetrics(mc); + if (m != null) { + m.incrementMessagesSent(); + } + } + + public void incrementFaultsSending(int errorCode, MessageContext mc) { + incrementFaultsSending(); + MessageLevelMetricsCollector m = getMsgLevelMetrics(mc); + if (m != null) { + m.incrementFaultsSending(errorCode); + } + } + + public void incrementTimeoutsSending(MessageContext mc) { + incrementTimeoutsSending(); + MessageLevelMetricsCollector m = getMsgLevelMetrics(mc); + if (m != null) { + m.incrementTimeoutsSending(); + } + } + + public void incrementBytesSent(MessageContext mc, long size) { + incrementBytesSent(size); + MessageLevelMetricsCollector m = getMsgLevelMetrics(mc); + if (m != null) { + m.incrementBytesSent(size); + } + } + + public void notifyReceivedMessageSize(MessageContext mc, long size) { + notifyReceivedMessageSize(size); + + MessageLevelMetricsCollector m = getMsgLevelMetrics(mc); + if (m != null) { + m.notifyReceivedMessageSize(size); + } + } + + public void notifySentMessageSize(MessageContext mc, long size) { + notifySentMessageSize(size); + + MessageLevelMetricsCollector m = getMsgLevelMetrics(mc); + if (m != null) { + m.notifySentMessageSize(size); + } + } + + public void reportResponseCode(MessageContext mc, int respCode) { + reportResponseCode(respCode); + + MessageLevelMetricsCollector m = getMsgLevelMetrics(mc); + if (m != null) { + m.reportResponseCode(respCode); + } + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/ParamUtils.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/ParamUtils.java new file mode 100644 index 0000000000..7c77d553fc --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/ParamUtils.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.sca.binding.ws.axis2.transport.base; + +import org.apache.axis2.AxisFault; +import org.apache.axis2.description.AxisService; +import org.apache.axis2.description.Parameter; +import org.apache.axis2.description.ParameterInclude; +import org.apache.axis2.description.TransportInDescription; +import org.apache.axis2.description.TransportOutDescription; +import org.apache.axis2.util.JavaUtils; + +/** + * Utility class with methods to manipulate service or transport parameters. + */ +public class ParamUtils { + private ParamUtils() {} + + public static String getRequiredParam(ParameterInclude paramInclude, String paramName) throws AxisFault { + Parameter param = paramInclude.getParameter(paramName); + if (param != null && param.getValue() != null && param.getValue() instanceof String) { + return (String) param.getValue(); + } else { + throw new AxisFault("Cannot find parameter '" + paramName + "' for " + + getDescriptionFor(paramInclude)); + } + } + + public static String getOptionalParam(ParameterInclude paramInclude, String paramName) throws AxisFault { + Parameter param = paramInclude.getParameter(paramName); + if (param != null && param.getValue() != null && param.getValue() instanceof String) { + return (String) param.getValue(); + } else { + return null; + } + } + + public static Integer getOptionalParamInt(ParameterInclude paramInclude, String paramName) throws AxisFault { + Parameter param = paramInclude.getParameter(paramName); + if (param == null || param.getValue() == null) { + return null; + } else { + Object paramValue = param.getValue(); + if (paramValue instanceof Integer) { + return (Integer)paramValue; + } else if (paramValue instanceof String) { + try { + return Integer.valueOf((String)paramValue); + } catch (NumberFormatException ex) { + throw new AxisFault("Invalid value '" + paramValue + "' for parameter '" + paramName + + "' for " + getDescriptionFor(paramInclude)); + } + } else { + throw new AxisFault("Invalid type for parameter '" + paramName + "' for " + + getDescriptionFor(paramInclude)); + } + } + } + + public static int getOptionalParamInt(ParameterInclude paramInclude, String paramName, int defaultValue) throws AxisFault { + Integer value = getOptionalParamInt(paramInclude, paramName); + return value == null ? defaultValue : value.intValue(); + } + + public static boolean getOptionalParamBoolean(ParameterInclude paramInclude, String paramName, boolean defaultValue) throws AxisFault { + Parameter param = paramInclude.getParameter(paramName); + return param == null ? defaultValue : JavaUtils.isTrueExplicitly(param.getValue(), defaultValue); + } + + public static int getRequiredParamInt(ParameterInclude paramInclude, String paramName) throws AxisFault { + Integer value = getOptionalParamInt(paramInclude, paramName); + if (value == null) { + throw new AxisFault("Cannot find parameter '" + paramName + + "' for " + getDescriptionFor(paramInclude)); + } else { + return value.intValue(); + } + } + + private static String getDescriptionFor(ParameterInclude paramInclude) { + if (paramInclude instanceof AxisService) { + return "service '" + ((AxisService)paramInclude).getName() + "'"; + } else if (paramInclude instanceof TransportInDescription) { + return "transport receiver '" + ((TransportInDescription)paramInclude).getName() + "'"; + } else if (paramInclude instanceof TransportOutDescription) { + return "transport sender '" + ((TransportOutDescription)paramInclude).getName() + "'"; + } else { + return paramInclude.getClass().getName(); + } + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/SynchronousCallback.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/SynchronousCallback.java new file mode 100644 index 0000000000..1016e88a82 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/SynchronousCallback.java @@ -0,0 +1,109 @@ +/* + * Copyright 2004,2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tuscany.sca.binding.ws.axis2.transport.base; + +import org.apache.axis2.AxisFault; +import org.apache.axis2.context.MessageContext; +import org.apache.axis2.context.OperationContext; +import org.apache.axis2.description.AxisMessage; +import org.apache.axis2.description.AxisOperation; +import org.apache.axis2.wsdl.WSDLConstants; + + +public class SynchronousCallback { + + private MessageContext outMessageContext; + private MessageContext inMessageContext; + + private boolean isComplete; + + public SynchronousCallback(MessageContext outMessageContext) { + this.outMessageContext = outMessageContext; + this.isComplete = false; + } + + public synchronized void setInMessageContext(MessageContext inMessageContext) throws AxisFault { + + // if some other thread has access and complete then return without doing any thing. + // thread should have activate by the first message. + if (!isComplete) { + // this code is invoked only if the code use with axis2 at the client side + // when axis2 client receive messages it waits in the sending thread until the response comes. + // so this thread only notify the waiting thread and hence we need to build the message here. + inMessageContext.getEnvelope().build(); + OperationContext operationContext = outMessageContext.getOperationContext(); + MessageContext msgCtx = + operationContext.getMessageContext(WSDLConstants.MESSAGE_LABEL_IN_VALUE); + + if (msgCtx == null) { + // try to see whether there is a piggy back message context + if (outMessageContext.getProperty(org.apache.axis2.Constants.PIGGYBACK_MESSAGE) != null) { + + msgCtx = (MessageContext) outMessageContext.getProperty(org.apache.axis2.Constants.PIGGYBACK_MESSAGE); + msgCtx.setTransportIn(inMessageContext.getTransportIn()); + msgCtx.setTransportOut(inMessageContext.getTransportOut()); + msgCtx.setServerSide(false); + msgCtx.setProperty(BaseConstants.MAIL_CONTENT_TYPE, + inMessageContext.getProperty(BaseConstants.MAIL_CONTENT_TYPE)); + // FIXME: this class must not be transport dependent since it is used by AbstractTransportListener + msgCtx.setIncomingTransportName(org.apache.axis2.Constants.TRANSPORT_MAIL); + msgCtx.setEnvelope(inMessageContext.getEnvelope()); + + } else { + inMessageContext.setOperationContext(operationContext); + inMessageContext.setServiceContext(outMessageContext.getServiceContext()); + if (!operationContext.isComplete()) { + operationContext.addMessageContext(inMessageContext); + } + AxisOperation axisOp = operationContext.getAxisOperation(); + AxisMessage inMessage = axisOp.getMessage(WSDLConstants.MESSAGE_LABEL_IN_VALUE); + inMessageContext.setAxisMessage(inMessage); + inMessageContext.setServerSide(false); + } + + } else { + msgCtx.setOperationContext(operationContext); + msgCtx.setServiceContext(outMessageContext.getServiceContext()); + AxisOperation axisOp = operationContext.getAxisOperation(); + AxisMessage inMessage = axisOp.getMessage(WSDLConstants.MESSAGE_LABEL_IN_VALUE); + msgCtx.setAxisMessage(inMessage); + msgCtx.setTransportIn(inMessageContext.getTransportIn()); + msgCtx.setTransportOut(inMessageContext.getTransportOut()); + msgCtx.setServerSide(false); + msgCtx.setProperty(BaseConstants.MAIL_CONTENT_TYPE, + inMessageContext.getProperty(BaseConstants.MAIL_CONTENT_TYPE)); + // FIXME: this class must not be transport dependent since it is used by AbstractTransportListener + msgCtx.setIncomingTransportName(org.apache.axis2.Constants.TRANSPORT_MAIL); + msgCtx.setEnvelope(inMessageContext.getEnvelope()); + + } + this.inMessageContext = inMessageContext; + isComplete = true; + this.notifyAll(); + } + + } + + + public boolean isComplete() { + return isComplete; + } + + public void setComplete(boolean complete) { + isComplete = complete; + } + +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/TransportListenerEndpointView.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/TransportListenerEndpointView.java new file mode 100644 index 0000000000..8919aea5e7 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/TransportListenerEndpointView.java @@ -0,0 +1,61 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.transport.base; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +import org.apache.axis2.AxisFault; +import org.apache.axis2.addressing.EndpointReference; + +public class TransportListenerEndpointView implements TransportListenerEndpointViewMBean { + private final AbstractTransportListener listener; + private final String serviceName; + + public TransportListenerEndpointView(AbstractTransportListener listener, String serviceName) { + this.listener = listener; + this.serviceName = serviceName; + } + + public String[] getAddresses() { + String hostname; + try { + hostname = InetAddress.getLocalHost().getHostName(); + } + catch (UnknownHostException ex) { + hostname = "localhost"; + } + EndpointReference[] epr; + try { + epr = listener.getEPRsForService(serviceName, hostname); + } + catch (AxisFault ex) { + return null; + } + if (epr == null) { + return null; + } else { + String[] result = new String[epr.length]; + for (int i=0; i<epr.length; i++) { + result[i] = epr[i].getAddress(); + } + return result; + } + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/TransportListenerEndpointViewMBean.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/TransportListenerEndpointViewMBean.java new file mode 100644 index 0000000000..71b9618977 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/TransportListenerEndpointViewMBean.java @@ -0,0 +1,23 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ +package org.apache.tuscany.sca.binding.ws.axis2.transport.base; + +public interface TransportListenerEndpointViewMBean { + String[] getAddresses(); +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/TransportMBeanSupport.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/TransportMBeanSupport.java new file mode 100644 index 0000000000..bb23c5f96d --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/TransportMBeanSupport.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.transport.base; + +import java.lang.management.ManagementFactory; + +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; + +import org.apache.axis2.transport.TransportListener; +import org.apache.axis2.transport.TransportSender; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Support class to register MBeans for transport listeners and senders. + * This class can be used by {@link TransportListener} and {@link TransportSender} classes + * to register the {@link TransportView} management bean. It takes care of registering + * the bean under a consistent name and makes sure that a JMX related error doesn't stop + * the transport from working: a failure to register the MBean will cause JMX support + * to be disabled. + */ +public class TransportMBeanSupport { + private static final Log log = LogFactory.getLog(TransportMBeanSupport.class); + + private boolean enabled = true; + private boolean registered; + private MBeanServer mbs; + private ObjectName mbeanName; + private TransportView mbeanInstance; + + private TransportMBeanSupport(String connectorName, TransportView mbeanInstance) { + try { + mbs = ManagementFactory.getPlatformMBeanServer(); + } catch (SecurityException ex) { + log.warn("Unable to get the platform MBean server; JMX support disabled", ex); + enabled = false; + return; + } + String jmxAgentName = System.getProperty("jmx.agent.name"); + if (jmxAgentName == null || "".equals(jmxAgentName)) { + jmxAgentName = "org.apache.axis2"; + } + String mbeanNameString = jmxAgentName + ":Type=Transport,ConnectorName=" + connectorName; + try { + mbeanName = ObjectName.getInstance(mbeanNameString); + } catch (MalformedObjectNameException ex) { + log.warn("Unable to create object name '" + mbeanNameString + + "'; JMX support disabled", ex); + enabled = false; + } + this.mbeanInstance = mbeanInstance; + } + + public TransportMBeanSupport(TransportListener listener, String name) { + this(name + "-listener", new TransportView(listener, null)); + } + + public TransportMBeanSupport(TransportSender sender, String name) { + this(name + "-sender", new TransportView(null, sender)); + } + + public ObjectName getMBeanName() { + return mbeanName; + } + + /** + * Register the {@link TransportView} MBean. + */ + public void register() { + if (enabled && !registered) { + try { + mbs.registerMBean(mbeanInstance, mbeanName); + registered = true; + } catch (Exception e) { + log.warn("Error registering a MBean with objectname ' " + mbeanName + + " ' for JMX management", e); + enabled = false; + } + } + } + + /** + * Unregister the {@link TransportView} MBean. + */ + public void unregister() { + if (enabled && registered) { + try { + mbs.unregisterMBean(mbeanName); + registered = false; + } catch (Exception e) { + log.warn("Error un-registering a MBean with objectname ' " + mbeanName + + " ' for JMX management", e); + } + } + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/TransportView.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/TransportView.java new file mode 100644 index 0000000000..0724110ed3 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/TransportView.java @@ -0,0 +1,264 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.transport.base; + +import java.util.Map; + +import org.apache.axis2.transport.TransportListener; +import org.apache.axis2.transport.TransportSender; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class TransportView implements TransportViewMBean { + + private static final Log log = LogFactory.getLog(TransportView.class); + + public static final int STOPPED = 0; + public static final int RUNNING = 1; + public static final int PAUSED = 2; + public static final int SHUTTING_DOWN = 3; + + private TransportListener listener = null; + private TransportSender sender = null; + + public TransportView(TransportListener listener, TransportSender sender) { + this.listener = listener; + this.sender = sender; + } + + // JMX Attributes + public long getMessagesReceived() { + if (listener != null && listener instanceof ManagementSupport) { + return ((ManagementSupport) listener).getMessagesReceived(); + } else if (sender != null && sender instanceof ManagementSupport) { + return ((ManagementSupport) sender).getMessagesReceived(); + } + return -1; + } + + public long getFaultsReceiving() { + if (listener != null && listener instanceof ManagementSupport) { + return ((ManagementSupport) listener).getFaultsReceiving(); + } else if (sender != null && sender instanceof ManagementSupport) { + return ((ManagementSupport) sender).getFaultsReceiving(); + } + return -1; + } + + public long getTimeoutsReceiving() { + if (listener != null && listener instanceof ManagementSupport) { + return ((ManagementSupport) listener).getTimeoutsReceiving(); + } else if (sender != null && sender instanceof ManagementSupport) { + return ((ManagementSupport) sender).getTimeoutsReceiving(); + } + return -1; + } + + public long getTimeoutsSending() { + if (listener != null && listener instanceof ManagementSupport) { + return ((ManagementSupport) listener).getTimeoutsSending(); + } else if (sender != null && sender instanceof ManagementSupport) { + return ((ManagementSupport) sender).getTimeoutsSending(); + } + return -1; + } + + public long getBytesReceived() { + if (listener != null && listener instanceof ManagementSupport) { + return ((ManagementSupport) listener).getBytesReceived(); + } else if (sender != null && sender instanceof ManagementSupport) { + return ((ManagementSupport) sender).getBytesReceived(); + } + return -1; + } + + public long getMessagesSent() { + if (listener != null && listener instanceof ManagementSupport) { + return ((ManagementSupport) listener).getMessagesSent(); + } else if (sender != null && sender instanceof ManagementSupport) { + return ((ManagementSupport) sender).getMessagesSent(); + } + return -1; + } + + public long getFaultsSending() { + if (listener != null && listener instanceof ManagementSupport) { + return ((ManagementSupport) listener).getFaultsSending(); + } else if (sender != null && sender instanceof ManagementSupport) { + return ((ManagementSupport) sender).getFaultsSending(); + } + return -1; + } + + public long getBytesSent() { + if (listener != null && listener instanceof ManagementSupport) { + return ((ManagementSupport) listener).getBytesSent(); + } else if (sender != null && sender instanceof ManagementSupport) { + return ((ManagementSupport) sender).getBytesSent(); + } + return -1; + } + + public long getMinSizeReceived() { + if (listener != null && listener instanceof ManagementSupport) { + return ((ManagementSupport) listener).getMinSizeReceived(); + } else if (sender != null && sender instanceof ManagementSupport) { + return ((ManagementSupport) sender).getMinSizeReceived(); + } + return -1; + } + + public long getMaxSizeReceived() { + if (listener != null && listener instanceof ManagementSupport) { + return ((ManagementSupport) listener).getMaxSizeReceived(); + } else if (sender != null && sender instanceof ManagementSupport) { + return ((ManagementSupport) sender).getMaxSizeReceived(); + } + return -1; + } + + public double getAvgSizeReceived() { + if (listener != null && listener instanceof ManagementSupport) { + return ((ManagementSupport) listener).getAvgSizeReceived(); + } else if (sender != null && sender instanceof ManagementSupport) { + return ((ManagementSupport) sender).getAvgSizeReceived(); + } + return -1; + } + + public long getMinSizeSent() { + if (listener != null && listener instanceof ManagementSupport) { + return ((ManagementSupport) listener).getMinSizeSent(); + } else if (sender != null && sender instanceof ManagementSupport) { + return ((ManagementSupport) sender).getMinSizeSent(); + } + return -1; + } + + public long getMaxSizeSent() { + if (listener != null && listener instanceof ManagementSupport) { + return ((ManagementSupport) listener).getMaxSizeSent(); + } else if (sender != null && sender instanceof ManagementSupport) { + return ((ManagementSupport) sender).getMaxSizeSent(); + } + return -1; + } + + public double getAvgSizeSent() { + if (listener != null && listener instanceof ManagementSupport) { + return ((ManagementSupport) listener).getAvgSizeSent(); + } else if (sender != null && sender instanceof ManagementSupport) { + return ((ManagementSupport) sender).getAvgSizeSent(); + } + return -1; + } + + public Map getResponseCodeTable() { + if (listener != null && listener instanceof ManagementSupport) { + return ((ManagementSupport) listener).getResponseCodeTable(); + } else if (sender != null && sender instanceof ManagementSupport) { + return ((ManagementSupport) sender).getResponseCodeTable(); + } + return null; + } + + public int getActiveThreadCount() { + if (listener != null && listener instanceof ManagementSupport) { + return ((ManagementSupport) listener).getActiveThreadCount(); + } else if (sender != null && sender instanceof ManagementSupport) { + return ((ManagementSupport) sender).getActiveThreadCount(); + } + return -1; + } + + public int getQueueSize() { + if (listener != null && listener instanceof ManagementSupport) { + return ((ManagementSupport) listener).getQueueSize(); + } else if (sender != null && sender instanceof ManagementSupport) { + return ((ManagementSupport) sender).getQueueSize(); + } + return -1; + } + + // JMX Operations + public void start() throws Exception{ + if (listener != null) { + listener.start(); + } + } + + public void stop() throws Exception { + if (listener != null) { + listener.stop(); + } else if (sender != null) { + sender.stop(); + } + } + + public void pause() throws Exception { + if (listener instanceof ManagementSupport) { + ((ManagementSupport) listener).pause(); + } else if (sender instanceof ManagementSupport) { + ((ManagementSupport) sender).pause(); + } + } + + public void resume() throws Exception { + if (listener instanceof ManagementSupport) { + ((ManagementSupport) listener).resume(); + } else if (sender instanceof ManagementSupport) { + ((ManagementSupport) sender).resume(); + } + } + + public void maintenenceShutdown(long seconds) throws Exception { + if (listener instanceof ManagementSupport) { + ((ManagementSupport) listener).maintenenceShutdown(seconds * 1000); + } else if (sender instanceof ManagementSupport) { + ((ManagementSupport) sender).maintenenceShutdown(seconds * 1000); + } + } + + public void resetStatistics() { + if (listener != null && listener instanceof ManagementSupport) { + ((ManagementSupport) listener).resetStatistics(); + } else if (sender != null && sender instanceof ManagementSupport) { + ((ManagementSupport) sender).resetStatistics(); + } + } + + public long getLastResetTime() { + if (listener != null && listener instanceof ManagementSupport) { + return ((ManagementSupport) listener).getLastResetTime(); + } else if (sender != null && sender instanceof ManagementSupport) { + return ((ManagementSupport) sender).getLastResetTime(); + } + return -1; + } + + public long getMetricsWindow() { + if (listener != null && listener instanceof ManagementSupport) { + return System.currentTimeMillis() - ((ManagementSupport) listener).getLastResetTime(); + } else if (sender != null && sender instanceof ManagementSupport) { + return System.currentTimeMillis() - ((ManagementSupport) sender).getLastResetTime(); + } + return -1; + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/TransportViewMBean.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/TransportViewMBean.java new file mode 100644 index 0000000000..2aa86c1e8e --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/TransportViewMBean.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.transport.base; +import java.util.Map; + +public interface TransportViewMBean { + + // JMX Attributes + public long getMessagesReceived(); + public long getFaultsReceiving(); + public long getTimeoutsReceiving(); + public long getMessagesSent(); + public long getFaultsSending(); + public long getTimeoutsSending(); + public long getBytesReceived(); + public long getBytesSent(); + public long getMinSizeReceived(); + public long getMaxSizeReceived(); + public double getAvgSizeReceived(); + public long getMinSizeSent(); + public long getMaxSizeSent(); + public double getAvgSizeSent(); + public int getActiveThreadCount(); + public int getQueueSize(); + public Map getResponseCodeTable(); + + // JMX Operations + public void start() throws Exception; + public void stop() throws Exception; + public void pause() throws Exception; + public void resume() throws Exception; + public void maintenenceShutdown(long seconds) throws Exception; + + public void resetStatistics(); + public long getLastResetTime(); + public long getMetricsWindow(); +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/datagram/AbstractDatagramTransportListener.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/datagram/AbstractDatagramTransportListener.java new file mode 100644 index 0000000000..92cca71a79 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/datagram/AbstractDatagramTransportListener.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.sca.binding.ws.axis2.transport.base.datagram; + +import java.io.IOException; +import java.net.SocketException; +import java.util.HashMap; +import java.util.Map; + +import org.apache.axis2.AxisFault; +import org.apache.axis2.addressing.EndpointReference; +import org.apache.axis2.context.ConfigurationContext; +import org.apache.axis2.description.AxisService; +import org.apache.axis2.description.TransportInDescription; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.AbstractTransportListener; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.ParamUtils; + +public abstract class AbstractDatagramTransportListener<E extends DatagramEndpoint> + extends AbstractTransportListener { + + private final Map<String,E> endpoints = new HashMap<String,E>(); + private DatagramDispatcher<E> dispatcher; + private String defaultIp; + + @Override + public void init(ConfigurationContext cfgCtx, TransportInDescription transportIn) + throws AxisFault { + + super.init(cfgCtx, transportIn); + DatagramDispatcherCallback callback = new DatagramDispatcherCallback() { + public void receive(DatagramEndpoint endpoint, byte[] data, int length) { + workerPool.execute(new ProcessPacketTask(endpoint, data, length)); + } + }; + try { + dispatcher = createDispatcher(callback); + } catch (IOException ex) { + throw new AxisFault("Unable to create selector", ex); + } + try { + defaultIp = org.apache.axis2.util.Utils.getIpAddress(cfgCtx.getAxisConfiguration()); + } catch (SocketException ex) { + throw new AxisFault("Unable to determine the host's IP address", ex); + } + } + + @Override + protected void startListeningForService(AxisService service) throws AxisFault { + E endpoint = createEndpoint(service); + endpoint.setListener(this); + endpoint.setService(service); + endpoint.setContentType(ParamUtils.getRequiredParam( + service, "transport." + getTransportName() + ".contentType")); + endpoint.setMetrics(metrics); + + try { + dispatcher.addEndpoint(endpoint); + } catch (IOException ex) { + throw new AxisFault("Unable to listen on endpoint " + + endpoint.getEndpointReference(defaultIp), ex); + } + if (log.isDebugEnabled()) { + log.debug("Started listening on endpoint " + endpoint.getEndpointReference(defaultIp) + + " [contentType=" + endpoint.getContentType() + + "; service=" + service.getName() + "]"); + } + endpoints.put(service.getName(), endpoint); + } + + @Override + protected void stopListeningForService(AxisService service) { + try { + dispatcher.removeEndpoint(endpoints.get(service.getName())); + } catch (IOException ex) { + log.error("I/O exception while stopping listener for service " + service.getName(), ex); + } + endpoints.remove(service.getName()); + } + + @Override + public void destroy() { + super.destroy(); + try { + dispatcher.stop(); + } catch (IOException ex) { + log.error("Failed to stop dispatcher", ex); + } + } + + public EndpointReference[] getEPRsForService(String serviceName, String ip) throws AxisFault { + + // strip out the endpoint name if present + if (serviceName.indexOf('.') != -1) { + serviceName = serviceName.substring(0, serviceName.indexOf('.')); + } + + E endpoint = endpoints.get(serviceName); + if (endpoint == null) { + return null; + } else { + return new EndpointReference[] { + endpoint.getEndpointReference(ip == null ? defaultIp : ip) }; + } + } + + protected abstract DatagramDispatcher<E> createDispatcher(DatagramDispatcherCallback callback) + throws IOException; + + protected abstract E createEndpoint(AxisService service) throws AxisFault; +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/datagram/DatagramDispatcher.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/datagram/DatagramDispatcher.java new file mode 100644 index 0000000000..e19aec4483 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/datagram/DatagramDispatcher.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.tuscany.sca.binding.ws.axis2.transport.base.datagram; + +import java.io.IOException; + +public interface DatagramDispatcher<E> { + void addEndpoint(E endpoint) throws IOException; + void removeEndpoint(E endpoint) throws IOException; + void stop() throws IOException; +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/datagram/DatagramDispatcherCallback.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/datagram/DatagramDispatcherCallback.java new file mode 100644 index 0000000000..691fe2fef9 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/datagram/DatagramDispatcherCallback.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.sca.binding.ws.axis2.transport.base.datagram; + +public interface DatagramDispatcherCallback { + void receive(DatagramEndpoint endpoint, byte[] data, int length); +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/datagram/DatagramEndpoint.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/datagram/DatagramEndpoint.java new file mode 100644 index 0000000000..95eebbca06 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/datagram/DatagramEndpoint.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.tuscany.sca.binding.ws.axis2.transport.base.datagram; + +import org.apache.axis2.addressing.EndpointReference; +import org.apache.axis2.description.AxisService; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.MetricsCollector; + +/** + * Endpoint description. + * This class is used by the transport to store information + * about an endpoint, e.g. the Axis service it is bound to. + * Transports extend this abstract class to store additional + * transport specific information, such as the port number + * the transport listens on. + */ +public abstract class DatagramEndpoint { + private AbstractDatagramTransportListener listener; + private String contentType; + private AxisService service; + private MetricsCollector metrics; + + public AbstractDatagramTransportListener getListener() { + return listener; + } + + public void setListener(AbstractDatagramTransportListener listener) { + this.listener = listener; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public AxisService getService() { + return service; + } + + public void setService(AxisService service) { + this.service = service; + } + + public MetricsCollector getMetrics() { + return metrics; + } + + public void setMetrics(MetricsCollector metrics) { + this.metrics = metrics; + } + + public abstract EndpointReference getEndpointReference(String ip); +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/datagram/ProcessPacketTask.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/datagram/ProcessPacketTask.java new file mode 100644 index 0000000000..bb102317b8 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/datagram/ProcessPacketTask.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.tuscany.sca.binding.ws.axis2.transport.base.datagram; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import org.apache.axiom.soap.SOAPEnvelope; +import org.apache.axis2.context.MessageContext; +import org.apache.axis2.engine.AxisEngine; +import org.apache.axis2.transport.TransportUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.MetricsCollector; + +/** + * Task encapsulating the processing of a datagram. + * Instances of this class will be dispatched to worker threads for + * execution. + */ +public class ProcessPacketTask implements Runnable { + private static final Log log = LogFactory.getLog(ProcessPacketTask.class); + + private final DatagramEndpoint endpoint; + private final byte[] data; + private final int length; + + public ProcessPacketTask(DatagramEndpoint endpoint, byte[] data, int length) { + this.endpoint = endpoint; + this.data = data; + this.length = length; + } + + public void run() { + MetricsCollector metrics = endpoint.getMetrics(); + try { + InputStream inputStream = new ByteArrayInputStream(data, 0, length); + MessageContext msgContext = endpoint.getListener().createMessageContext(); + msgContext.setAxisService(endpoint.getService()); + SOAPEnvelope envelope = TransportUtils.createSOAPMessage(msgContext, inputStream, endpoint.getContentType()); + msgContext.setEnvelope(envelope); + AxisEngine.receive(msgContext); + metrics.incrementMessagesReceived(); + metrics.incrementBytesReceived(length); + } catch (Exception ex) { + metrics.incrementFaultsReceiving(); + StringBuilder buffer = new StringBuilder("Error during processing of datagram:\n"); + Utils.hexDump(buffer, data, length); + log.error(buffer.toString(), ex); + } + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/datagram/Utils.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/datagram/Utils.java new file mode 100644 index 0000000000..41230bbcdc --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/datagram/Utils.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.sca.binding.ws.axis2.transport.base.datagram; + +/** + * Utility class with methods used by datagram transports. + */ +public class Utils { + private Utils() {} + + public static void hexDump(StringBuilder buffer, byte[] data, int length) { + for (int start = 0; start < length; start += 16) { + for (int i=0; i<16; i++) { + int index = start+i; + if (index < length) { + String hex = Integer.toHexString(data[start+i] & 0xFF); + if (hex.length() < 2) { + buffer.append('0'); + } + buffer.append(hex); + } else { + buffer.append(" "); + } + buffer.append(' '); + if (i == 7) { + buffer.append(' '); + } + } + buffer.append(" |"); + for (int i=0; i<16; i++) { + int index = start+i; + if (index < length) { + int b = data[index] & 0xFF; + if (32 <= b && b < 128) { + buffer.append((char)b); + } else { + buffer.append('.'); + } + } else { + buffer.append(' '); + } + } + buffer.append('|'); + buffer.append('\n'); + } + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/datagram/package-info.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/datagram/package-info.java new file mode 100644 index 0000000000..a5765e7e55 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/datagram/package-info.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. + */ + +/** + * Base classes for datagram transports. + * <p> + * A datagram type transport is a transport that entirely reads a message + * into memory before starting to process it: in contrast to transports like HTTP, + * it doesn't support streaming. This approach can be chosen either because + * of the characteristics of the underlying protocol (such as in the case of UDP) + * or because streaming a message would unnecessarily delay the processing of the + * next available message (as in the case of a UNIX pipe). + */ +package org.apache.tuscany.sca.binding.ws.axis2.transport.base.datagram; diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/event/TransportError.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/event/TransportError.java new file mode 100644 index 0000000000..5de216163f --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/event/TransportError.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.transport.base.event; + +import org.apache.axis2.description.AxisService; + +public class TransportError { + private final Object source; + private final AxisService service; + private final Throwable exception; + + public TransportError(Object source, AxisService service, Throwable exception) { + this.source = source; + this.service = service; + this.exception = exception; + } + + public Object getSource() { + return source; + } + + public AxisService getService() { + return service; + } + + public Throwable getException() { + return exception; + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/event/TransportErrorListener.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/event/TransportErrorListener.java new file mode 100644 index 0000000000..40d46dba56 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/event/TransportErrorListener.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.transport.base.event; + +public interface TransportErrorListener { + void error(TransportError error); +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/event/TransportErrorSource.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/event/TransportErrorSource.java new file mode 100644 index 0000000000..141ca20099 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/event/TransportErrorSource.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.transport.base.event; + +public interface TransportErrorSource { + void addErrorListener(TransportErrorListener listener); + void removeErrorListener(TransportErrorListener listener); +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/event/TransportErrorSourceSupport.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/event/TransportErrorSourceSupport.java new file mode 100644 index 0000000000..274b7b7b90 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/event/TransportErrorSourceSupport.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.transport.base.event; + +import java.util.LinkedList; +import java.util.List; + +import org.apache.axis2.description.AxisService; + +public class TransportErrorSourceSupport implements TransportErrorSource { + private final Object source; + private final List<TransportErrorListener> listeners = new LinkedList<TransportErrorListener>(); + + public TransportErrorSourceSupport(Object source) { + this.source = source; + } + + public synchronized void addErrorListener(TransportErrorListener listener) { + listeners.add(listener); + } + + public synchronized void removeErrorListener(TransportErrorListener listener) { + listeners.remove(listener); + } + + public synchronized void error(AxisService service, Throwable ex) { + if (!listeners.isEmpty()) { + TransportError error = new TransportError(source, service, ex); + for (TransportErrorListener listener : listeners) { + listener.error(error); + } + } + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/streams/ReaderInputStream.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/streams/ReaderInputStream.java new file mode 100644 index 0000000000..5047e1a499 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/streams/ReaderInputStream.java @@ -0,0 +1,229 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tuscany.sca.binding.ws.axis2.transport.base.streams; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CoderResult; + +/** + * {@link InputStream} implementation that reads a character stream from a {@link Reader} + * and transforms it to a byte stream using a specified charset encoding. The stream + * is transformed using a {@link CharsetEncoder} object, guaranteeing that all charset + * encodings supported by the JRE are handled correctly. In particular for charsets such as + * UTF-16, the implementation ensures that one and only one byte order marker + * is produced. + * <p> + * Since in general it is not possible to predict the number of characters to be read from the + * {@link Reader} to satisfy a read request on the {@link ReaderInputStream}, all reads from + * the {@link Reader} are buffered. There is therefore no well defined correlation + * between the current position of the {@link Reader} and that of the {@link ReaderInputStream}. + * This also implies that in general there is no need to wrap the underlying {@link Reader} + * in a {@link java.io.BufferedReader}. + * <p> + * {@link ReaderInputStream} implements the inverse transformation of {@link java.io.InputStreamReader}; + * in the following example, reading from <tt>in2</tt> would return the same byte + * sequence as reading from <tt>in</tt> (provided that the initial byte sequence is legal + * with respect to the charset encoding): + * <pre> + * InputStream in = ... + * Charset cs = ... + * InputStreamReader reader = new InputStreamReader(in, cs); + * ReaderInputStream in2 = new ReaderInputStream(reader, cs);</pre> + * {@link ReaderInputStream} implements the same transformation as {@link java.io.OutputStreamWriter}, + * except that the control flow is reversed: both classes transform a character stream + * into a byte stream, but {@link java.io.OutputStreamWriter} pushes data to the underlying stream, + * while {@link ReaderInputStream} pulls it from the underlying stream. + * <p> + * Note that while there are use cases where there is no alternative to using + * this class, very often the need to use this class is an indication of a flaw + * in the design of the code. This class is typically used in situations where an existing + * API only accepts an {@link InputStream}, but where the most natural way to produce the data + * is as a character stream, i.e. by providing a {@link Reader} instance. An example of a situation + * where this problem may appear is when implementing the {@link javax.activation.DataSource} + * interface from the Java Activation Framework. + * <p> + * Given the fact that the {@link Reader} class doesn't provide any way to predict whether the next + * read operation will block or not, it is not possible to provide a meaningful + * implementation of the {@link InputStream#available()} method. A call to this method + * will always return 0. Also, this class doesn't support {@link InputStream#mark(int)}. + * <p> + * Instances of {@link ReaderInputStream} are not thread safe. + */ +// NOTE: Remove this class once Commons IO 2.0 is available (see IO-158) +public class ReaderInputStream extends InputStream { + private static final int DEFAULT_BUFFER_SIZE = 1024; + + private final Reader reader; + private final CharsetEncoder encoder; + + /** + * CharBuffer used as input for the decoder. It should be reasonably + * large as we read data from the underlying Reader into this buffer. + */ + private final CharBuffer encoderIn; + + /** + * ByteBuffer used as output for the decoder. This buffer can be small + * as it is only used to transfer data from the decoder to the + * buffer provided by the caller. + */ + private final ByteBuffer encoderOut = ByteBuffer.allocate(128); + + private CoderResult lastCoderResult; + private boolean endOfInput; + + /** + * Construct a new {@link ReaderInputStream}. + * + * @param reader the target {@link Reader} + * @param charset the charset encoding + * @param bufferSize the size of the input buffer in number of characters + */ + public ReaderInputStream(Reader reader, Charset charset, int bufferSize) { + this.reader = reader; + encoder = charset.newEncoder(); + encoderIn = CharBuffer.allocate(bufferSize); + encoderIn.flip(); + } + + /** + * Construct a new {@link ReaderInputStream} with a default input buffer size of + * 1024 characters. + * + * @param reader the target {@link Reader} + * @param charset the charset encoding + */ + public ReaderInputStream(Reader reader, Charset charset) { + this(reader, charset, DEFAULT_BUFFER_SIZE); + } + + /** + * Construct a new {@link ReaderInputStream}. + * + * @param reader the target {@link Reader} + * @param charsetName the name of the charset encoding + * @param bufferSize the size of the input buffer in number of characters + */ + public ReaderInputStream(Reader reader, String charsetName, int bufferSize) { + this(reader, Charset.forName(charsetName), bufferSize); + } + + /** + * Construct a new {@link ReaderInputStream} with a default input buffer size of + * 1024 characters. + * + * @param reader the target {@link Reader} + * @param charsetName the name of the charset encoding + */ + public ReaderInputStream(Reader reader, String charsetName) { + this(reader, charsetName, DEFAULT_BUFFER_SIZE); + } + + /** + * Construct a new {@link ReaderInputStream} that uses the default character encoding + * with a default input buffer size of 1024 characters. + * + * @param reader the target {@link Reader} + */ + public ReaderInputStream(Reader reader) { + this(reader, Charset.defaultCharset()); + } + + /** + * Read the specified number of bytes into an array. + * + * @param b the byte array to read into + * @param off the offset to start reading bytes into + * @param len the number of bytes to read + * @return the number of bytes read or <code>-1</code> + * if the end of the stream has been reached + */ + @Override + public int read(byte[] b, int off, int len) throws IOException { + int read = 0; + while (len > 0) { + if (encoderOut.position() > 0) { + encoderOut.flip(); + int c = Math.min(encoderOut.remaining(), len); + encoderOut.get(b, off, c); + off += c; + len -= c; + read += c; + encoderOut.compact(); + } else { + if (!endOfInput && (lastCoderResult == null || lastCoderResult.isUnderflow())) { + encoderIn.compact(); + int position = encoderIn.position(); + // We don't use Reader#read(CharBuffer) here because it is more efficient + // to write directly to the underlying char array (the default implementation + // copies data to a temporary char array). + int c = reader.read(encoderIn.array(), position, encoderIn.remaining()); + if (c == -1) { + endOfInput = true; + } else { + encoderIn.position(position+c); + } + encoderIn.flip(); + } + lastCoderResult = encoder.encode(encoderIn, encoderOut, endOfInput); + if (endOfInput && encoderOut.position() == 0) { + break; + } + } + } + return read == 0 && endOfInput ? -1 : read; + } + + /** + * Read the specified number of bytes into an array. + * + * @param b the byte array to read into + * @return the number of bytes read or <code>-1</code> + * if the end of the stream has been reached + */ + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + /** + * Read a single byte. + * + * @return either the byte read or <code>-1</code> if the end of the stream + * has been reached + */ + @Override + public int read() throws IOException { + byte[] b = new byte[1]; + return read(b) == -1 ? -1 : b[0] & 0xFF; + } + + /** + * Close the stream. This method will cause the underlying {@link Reader} + * to be closed. + */ + @Override + public void close() throws IOException { + reader.close(); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/streams/WriterOutputStream.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/streams/WriterOutputStream.java new file mode 100644 index 0000000000..3b6370da63 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/streams/WriterOutputStream.java @@ -0,0 +1,257 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.transport.base.streams; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Writer; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CoderResult; +import java.nio.charset.CodingErrorAction; + +/** + * {@link OutputStream} implementation that transforms a byte stream to a + * character stream using a specified charset encoding and writes the resulting + * stream to a {@link Writer}. The stream is transformed using a + * {@link CharsetDecoder} object, guaranteeing that all charset + * encodings supported by the JRE are handled correctly. + * <p> + * The output of the {@link CharsetDecoder} is buffered using a fixed size buffer. + * This implies that the data is written to the underlying {@link Writer} in chunks + * that are no larger than the size of this buffer. By default, the buffer is + * flushed only when it overflows or when {@link #flush()} or {@link #close()} + * is called. In general there is therefore no need to wrap the underlying {@link Writer} + * in a {@link java.io.BufferedWriter}. {@link WriterOutputStream} can also + * be instructed to flush the buffer after each write operation. In this case, all + * available data is written immediately to the underlying {@link Writer}, implying that + * the current position of the {@link Writer} is correlated to the current position + * of the {@link WriterOutputStream}. + * <p> + * {@link WriterOutputStream} implements the inverse transformation of {@link java.io.OutputStreamWriter}; + * in the following example, writing to <tt>out2</tt> would have the same result as writing to + * <tt>out</tt> directly (provided that the byte sequence is legal with respect to the + * charset encoding): + * <pre> + * OutputStream out = ... + * Charset cs = ... + * OutputStreamWriter writer = new OutputStreamWriter(out, cs); + * WriterOutputStream out2 = new WriterOutputStream(writer, cs);</pre> + * {@link WriterOutputStream} implements the same transformation as {@link java.io.InputStreamReader}, + * except that the control flow is reversed: both classes transform a byte stream + * into a character stream, but {@link java.io.InputStreamReader} pulls data from the underlying stream, + * while {@link WriterOutputStream} pushes it to the underlying stream. + * <p> + * Note that while there are use cases where there is no alternative to using + * this class, very often the need to use this class is an indication of a flaw + * in the design of the code. This class is typically used in situations where an existing + * API only accepts an {@link OutputStream} object, but where the stream is known to represent + * character data that must be decoded for further use. + * <p> + * Instances of {@link WriterOutputStream} are not thread safe. + */ +//NOTE: Remove this class once Commons IO 2.0 is available (see IO-158) +public class WriterOutputStream extends OutputStream { + private static final int DEFAULT_BUFFER_SIZE = 1024; + + private final Writer writer; + private final CharsetDecoder decoder; + private final boolean writeImmediately; + + /** + * ByteBuffer used as input for the decoder. This buffer can be small + * as it is used only to transfer the received data to the + * decoder. + */ + private final ByteBuffer decoderIn = ByteBuffer.allocate(128); + + /** + * CharBuffer used as output for the decoder. It should be + * somewhat larger as we write from this buffer to the + * underlying Writer. + */ + private final CharBuffer decoderOut; + + /** + * Constructs a new {@link WriterOutputStream}. + * + * @param writer the target {@link Writer} + * @param charset the charset encoding + * @param bufferSize the size of the output buffer in number of characters + * @param writeImmediately If <tt>true</tt> the output buffer will be flushed after each + * write operation, i.e. all available data will be written to the + * underlying {@link Writer} immediately. If <tt>false</tt>, the + * output buffer will only be flushed when it overflows or when + * {@link #flush()} or {@link #close()} is called. + */ + public WriterOutputStream(Writer writer, Charset charset, int bufferSize, boolean writeImmediately) { + this.writer = writer; + decoder = charset.newDecoder(); + decoder.onMalformedInput(CodingErrorAction.REPLACE); + decoder.onUnmappableCharacter(CodingErrorAction.REPLACE); + decoder.replaceWith("?"); + this.writeImmediately = writeImmediately; + decoderOut = CharBuffer.allocate(bufferSize); + } + + /** + * Constructs a new {@link WriterOutputStream} with a default output buffer size of + * 1024 characters. The output buffer will only be flushed when it overflows or when + * {@link #flush()} or {@link #close()} is called. + * + * @param writer the target {@link Writer} + * @param charset the charset encoding + */ + public WriterOutputStream(Writer writer, Charset charset) { + this(writer, charset, DEFAULT_BUFFER_SIZE, false); + } + + /** + * Constructs a new {@link WriterOutputStream}. + * + * @param writer the target {@link Writer} + * @param charsetName the name of the charset encoding + * @param bufferSize the size of the output buffer in number of characters + * @param writeImmediately If <tt>true</tt> the output buffer will be flushed after each + * write operation, i.e. all available data will be written to the + * underlying {@link Writer} immediately. If <tt>false</tt>, the + * output buffer will only be flushed when it overflows or when + * {@link #flush()} or {@link #close()} is called. + */ + public WriterOutputStream(Writer writer, String charsetName, int bufferSize, boolean writeImmediately) { + this(writer, Charset.forName(charsetName), bufferSize, writeImmediately); + } + + /** + * Constructs a new {@link WriterOutputStream} with a default output buffer size of + * 1024 characters. The output buffer will only be flushed when it overflows or when + * {@link #flush()} or {@link #close()} is called. + * + * @param writer the target {@link Writer} + * @param charsetName the name of the charset encoding + */ + public WriterOutputStream(Writer writer, String charsetName) { + this(writer, charsetName, DEFAULT_BUFFER_SIZE, false); + } + + /** + * Constructs a new {@link WriterOutputStream} that uses the default character encoding + * and with a default output buffer size of 1024 characters. The output buffer will only + * be flushed when it overflows or when {@link #flush()} or {@link #close()} is called. + * + * @param writer the target {@link Writer} + */ + public WriterOutputStream(Writer writer) { + this(writer, Charset.defaultCharset(), DEFAULT_BUFFER_SIZE, false); + } + + /** + * Write bytes from the specified byte array to the stream. + * + * @param b the byte array containing the bytes to write + * @param off the start offset in the byte array + * @param len the number of bytes to write + */ + @Override + public void write(byte[] b, int off, int len) throws IOException { + while (len > 0) { + int c = Math.min(len, decoderIn.remaining()); + decoderIn.put(b, off, c); + processInput(false); + len -= c; + off += c; + } + if (writeImmediately) { + flushOutput(); + } + } + + /** + * Write bytes from the specified byte array to the stream. + * + * @param b the byte array containing the bytes to write + */ + @Override + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + /** + * Write a single byte to the stream. + * + * @param b the byte to write + */ + @Override + public void write(int b) throws IOException { + write(new byte[] { (byte)b }, 0, 1); + } + + /** + * Flush the stream. Any remaining content accumulated in the output buffer + * will be written to the underlying {@link Writer}. After that + * {@link Writer#flush()} will be called. + */ + @Override + public void flush() throws IOException { + flushOutput(); + writer.flush(); + } + + /** + * Close the stream. Any remaining content accumulated in the output buffer + * will be written to the underlying {@link Writer}. After that + * {@link Writer#close()} will be called. + */ + @Override + public void close() throws IOException { + processInput(true); + flushOutput(); + writer.close(); + } + + private void processInput(boolean endOfInput) throws IOException { + // Prepare decoderIn for reading + decoderIn.flip(); + CoderResult coderResult; + while (true) { + coderResult = decoder.decode(decoderIn, decoderOut, endOfInput); + if (coderResult.isOverflow()) { + flushOutput(); + } else if (coderResult.isUnderflow()) { + break; + } else { + // The decoder is configured to replace malformed input and unmappable characters, + // so we should not get here. + throw new IOException("Unexpected coder result"); + } + } + // Discard the bytes that have been read + decoderIn.compact(); + } + + private void flushOutput() throws IOException { + if (decoderOut.position() > 0) { + writer.write(decoderOut.array(), 0, decoderOut.position()); + decoderOut.rewind(); + } + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/threads/NativeThreadFactory.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/threads/NativeThreadFactory.java new file mode 100644 index 0000000000..290ca08471 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/threads/NativeThreadFactory.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.transport.base.threads; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * This is a simple ThreadFactory implementation using java.util.concurrent + * Creates threads with the given name prefix + */ +public class NativeThreadFactory implements + ThreadFactory { + + final ThreadGroup group; + final AtomicInteger count; + final String namePrefix; + + public NativeThreadFactory(final ThreadGroup group, final String namePrefix) { + super(); + this.count = new AtomicInteger(1); + this.group = group; + this.namePrefix = namePrefix; + } + + public Thread newThread(final Runnable runnable) { + StringBuffer buffer = new StringBuffer(); + buffer.append(this.namePrefix); + buffer.append('-'); + buffer.append(this.count.getAndIncrement()); + Thread t = new Thread(group, runnable, buffer.toString(), 0); + t.setDaemon(false); + t.setPriority(Thread.NORM_PRIORITY); + return t; + } +}
\ No newline at end of file diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/threads/NativeWorkerPool.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/threads/NativeWorkerPool.java new file mode 100644 index 0000000000..9401daee8e --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/threads/NativeWorkerPool.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.tuscany.sca.binding.ws.axis2.transport.base.threads; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Worker pool implementation based on java.util.concurrent in JDK 1.5 or later. + */ +public class NativeWorkerPool implements WorkerPool { + + static final Log log = LogFactory.getLog(NativeWorkerPool.class); + + private final ThreadPoolExecutor executor; + private final LinkedBlockingQueue<Runnable> blockingQueue; + + public NativeWorkerPool(int core, int max, int keepAlive, + int queueLength, String threadGroupName, String threadGroupId) { + + if (log.isDebugEnabled()) { + log.debug("Using native util.concurrent package.."); + } + blockingQueue = + (queueLength == -1 ? new LinkedBlockingQueue<Runnable>() + : new LinkedBlockingQueue<Runnable>(queueLength)); + executor = new ThreadPoolExecutor( + core, max, keepAlive, + TimeUnit.SECONDS, + blockingQueue, + new NativeThreadFactory(new ThreadGroup(threadGroupName), threadGroupId)); + } + + public void execute(final Runnable task) { + executor.execute(new Runnable() { + public void run() { + try { + task.run(); + } catch (Throwable t) { + log.error("Uncaught exception", t); + } + } + }); + } + + public int getActiveCount() { + return executor.getActiveCount(); + } + + public int getQueueSize() { + return blockingQueue.size(); + } + + public void shutdown(int timeout) throws InterruptedException { + executor.shutdown(); + executor.awaitTermination(timeout, TimeUnit.MILLISECONDS); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/threads/WorkerPool.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/threads/WorkerPool.java new file mode 100644 index 0000000000..61745545ba --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/threads/WorkerPool.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.transport.base.threads; + +public interface WorkerPool { + /** + * Asynchronously execute the given task using one of the threads of the worker pool. + * The task is expected to terminate gracefully, i.e. {@link Runnable#run()} should not + * throw an exception. Any uncaught exceptions should be logged by the worker pool + * implementation. + * + * @param task the task to execute + */ + public void execute(Runnable task); + + public int getActiveCount(); + public int getQueueSize(); + + /** + * Destroy the worker pool. The pool will immediately stop + * accepting new tasks. All previously submitted tasks will + * be executed. The method blocks until all tasks have + * completed execution, or the timeout occurs, or the current + * thread is interrupted, whichever happens first. + * + * @param timeout the timeout value in milliseconds + * @throws InterruptedException if the current thread was + * interrupted while waiting for pending tasks to + * finish execution + */ + public void shutdown(int timeout) throws InterruptedException; +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/threads/WorkerPoolFactory.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/threads/WorkerPoolFactory.java new file mode 100644 index 0000000000..f93c8580cd --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/threads/WorkerPoolFactory.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.transport.base.threads; + +/** + * Worker pool factory. + * For the moment this always creates {@link NativeWorkerPool} instances since + * we assume that we are running on Java 1.5 or above. + */ +public class WorkerPoolFactory { + + public static WorkerPool getWorkerPool(int core, int max, int keepAlive, + int queueLength, String threadGroupName, String threadGroupId) { + return new NativeWorkerPool( + core, max, keepAlive, queueLength, threadGroupName, threadGroupId); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/tracker/AxisServiceFilter.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/tracker/AxisServiceFilter.java new file mode 100644 index 0000000000..8d49a2a437 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/tracker/AxisServiceFilter.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.tuscany.sca.binding.ws.axis2.transport.base.tracker; + +import org.apache.axis2.description.AxisService; + +/** + * Filter for {@link AxisService} instances. This interface is used by + * {@link AxisServiceTracker}. + */ +public interface AxisServiceFilter { + /** + * Examine whether a given service matches the filter criteria. + * + * @param service the service to examine + * @return <code>true</code> if the service matches the filter criteria + */ + boolean matches(AxisService service); +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/tracker/AxisServiceTracker.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/tracker/AxisServiceTracker.java new file mode 100644 index 0000000000..fca85ee71e --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/tracker/AxisServiceTracker.java @@ -0,0 +1,245 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.transport.base.tracker; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Queue; +import java.util.Set; + +import org.apache.axiom.om.OMElement; +import org.apache.axis2.AxisFault; +import org.apache.axis2.description.AxisModule; +import org.apache.axis2.description.AxisService; +import org.apache.axis2.description.AxisServiceGroup; +import org.apache.axis2.description.Parameter; +import org.apache.axis2.engine.AxisConfiguration; +import org.apache.axis2.engine.AxisEvent; +import org.apache.axis2.engine.AxisObserver; + +/** + * <p>Tracks services deployed in a given {@link AxisConfiguration}. + * The tracker is configured with references to three objects:</p> + * <ol> + * <li>An {@link AxisConfiguration} to watch.</li> + * <li>An {@link AxisServiceFilter} restricting the services to track.</li> + * <li>An {@link AxisServiceTrackerListener} receiving tracking events.</li> + * </ol> + * <p>An instance of this class maintains an up-to-date list of services + * satisfying all of the following criteria:</p> + * <ol> + * <li>The service is deployed in the given {@link AxisConfiguration}.</li> + * <li>The service is started, i.e. {@link AxisService#isActive()} returns true.</li> + * <li>The service matches the criteria specified by the given + * {@link AxisServiceFilter} instance.</li> + * </ol> + * <p>Whenever a service appears on the list, the tracker will call + * {@link AxisServiceTrackerListener#serviceAdded(AxisService)}. When a service disappears, it + * will call {@link AxisServiceTrackerListener#serviceRemoved(AxisService)}.</p> + * <p>When the tracker is created, it is initially in the stopped state. In this state no + * events will be sent to the listener. It can be started using {@link #start()} and stopped again + * using {@link #stop()}. The tracker list is defined to be empty when the tracker is in the + * stopped state. This implies that a call to {@link #start()} will generate + * {@link AxisServiceTrackerListener#serviceAdded(AxisService)} events for all services that meet + * the above criteria at that point in time. In the same way, {@link #stop()} will generate + * {@link AxisServiceTrackerListener#serviceRemoved(AxisService)} events for the current entries + * in the list.</p> + * <p>As a corollary the tracker guarantees that during a complete lifecycle (start-stop), + * there will be exactly one {@link AxisServiceTrackerListener#serviceRemoved(AxisService)} event + * for every {@link AxisServiceTrackerListener#serviceAdded(AxisService)} event and vice-versa. + * This property is important when the tracker is used to allocate resources for a dynamic set + * of services.</p> + * + * <h2>Limitations</h2> + * + * <p>The tracker is not able to detect property changes on services. E.g. if a service initially + * matches the filter criteria, but later changes so that it doesn't match the criteria any more, + * the tracker will not be able to detect this and the service will not be removed from the tracker + * list.</p> + */ +public class AxisServiceTracker { + private final AxisObserver observer = new AxisObserver() { + public void init(AxisConfiguration axisConfig) {} + + public void serviceUpdate(AxisEvent event, final AxisService service) { + switch (event.getEventType()) { + case AxisEvent.SERVICE_DEPLOY: + case AxisEvent.SERVICE_START: + if (filter.matches(service)) { + boolean pending; + synchronized (lock) { + if (pending = (pendingActions != null)) { + pendingActions.add(new Runnable() { + public void run() { + serviceAdded(service); + } + }); + } + } + if (!pending) { + serviceAdded(service); + } + } + break; + case AxisEvent.SERVICE_REMOVE: + case AxisEvent.SERVICE_STOP: + // Don't check filter here because the properties of the service may have + // changed in the meantime. + boolean pending; + synchronized (lock) { + if (pending = (pendingActions != null)) { + pendingActions.add(new Runnable() { + public void run() { + serviceRemoved(service); + } + }); + } + } + if (!pending) { + serviceRemoved(service); + } + } + } + + public void moduleUpdate(AxisEvent event, AxisModule module) {} + public void addParameter(Parameter param) throws AxisFault {} + public void removeParameter(Parameter param) throws AxisFault {} + public void deserializeParameters(OMElement parameterElement) throws AxisFault {} + public Parameter getParameter(String name) { return null; } + public ArrayList<Parameter> getParameters() { return null; } + public boolean isParameterLocked(String parameterName) { return false; } + public void serviceGroupUpdate(AxisEvent event, AxisServiceGroup serviceGroup) {} + }; + + private final AxisConfiguration config; + final AxisServiceFilter filter; + private final AxisServiceTrackerListener listener; + + /** + * Object used to synchronize access to {@link #pendingActions} and {@link #services}. + */ + final Object lock = new Object(); + + /** + * Queue for notifications received by the {@link AxisObserver} during startup of the tracker. + * We need this because the events may already be reflected in the list of services returned + * by {@link AxisConfiguration#getServices()} (getting the list of currently deployed services + * and adding the observer can't be done atomically). It also allows us to make sure that + * events are sent to the listener in the right order, e.g. when a service is being removed + * during startup of the tracker. + */ + Queue<Runnable> pendingActions; + + /** + * The current list of services. <code>null</code> if the tracker is stopped. + */ + private Set<AxisService> services; + + public AxisServiceTracker(AxisConfiguration config, AxisServiceFilter filter, + AxisServiceTrackerListener listener) { + this.config = config; + this.filter = filter; + this.listener = listener; + } + + /** + * Check whether the tracker is started. + * + * @return <code>true</code> if the tracker is started + */ + public boolean isStarted() { + return services != null; + } + + /** + * Start the tracker. + * + * @throws IllegalStateException if the tracker has already been started + */ + public void start() { + if (services != null) { + throw new IllegalStateException(); + } + synchronized (lock) { + pendingActions = new LinkedList<Runnable>(); + config.addObservers(observer); + services = new HashSet<AxisService>(); + } + for (Object o : config.getServices().values()) { + AxisService service = (AxisService)o; + if (service.isActive() && filter.matches(service)) { + serviceAdded(service); + } + } + while (true) { + Runnable action; + synchronized (lock) { + action = pendingActions.poll(); + if (action == null) { + pendingActions = null; + break; + } + } + action.run(); + } + } + + void serviceAdded(AxisService service) { + // callListener may be false because the observer got an event for a service that + // was already in the initial list of services retrieved by AxisConfiguration#getServices. + boolean callListener; + synchronized (lock) { + callListener = services.add(service); + } + if (callListener) { + listener.serviceAdded(service); + } + } + + void serviceRemoved(AxisService service) { + // callListener may be false because the observer invokes this method without applying the + // filter. + boolean callListener; + synchronized (lock) { + callListener = services.remove(service); + } + if (callListener) { + listener.serviceRemoved(service); + } + } + + /** + * Stop the tracker. + * + * @throws IllegalStateException if the tracker is not started + */ + public void stop() { + if (services == null) { + throw new IllegalStateException(); + } + // TODO: This is very bad, but AxisConfiguration has no removeObserver method! + config.getObserversList().remove(observer); + for (AxisService service : services) { + listener.serviceRemoved(service); + } + services = null; + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/tracker/AxisServiceTrackerListener.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/tracker/AxisServiceTrackerListener.java new file mode 100644 index 0000000000..ccca293732 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/tracker/AxisServiceTrackerListener.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.ws.axis2.transport.base.tracker; + +import org.apache.axis2.description.AxisService; + +/** + * Listener for events generated by an {@link AxisServiceTracker}. + */ +public interface AxisServiceTrackerListener { + /** + * Inform the listener that a service has been added to tracker list. + * + * @param service the service that has been added to the tracker list + */ + void serviceAdded(AxisService service); + + /** + * Inform the listener that a service has been removed from the tracker list. + * + * @param service the service that has been removed from the tracker list + */ + void serviceRemoved(AxisService service); +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/tracker/package-info.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/tracker/package-info.java new file mode 100644 index 0000000000..eefadbbc1e --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/transport/base/tracker/package-info.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. + */ + +/** + * Contains utility classes to track a dynamic set of services deployed in an + * Axis configuration. + * + * @see org.apache.axis2.transport.base.tracker.AxisServiceTracker + */ +package org.apache.tuscany.sca.binding.ws.axis2.transport.base.tracker;
\ No newline at end of file |