diff --git a/build-spring-integration/build.xml b/build-spring-integration/build.xml new file mode 100644 index 0000000000..a7494d4711 --- /dev/null +++ b/build-spring-integration/build.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-integration-core/.classpath b/spring-integration-core/.classpath new file mode 100644 index 0000000000..9181e43c30 --- /dev/null +++ b/spring-integration-core/.classpath @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-core/.project b/spring-integration-core/.project new file mode 100644 index 0000000000..7d7a9e8356 --- /dev/null +++ b/spring-integration-core/.project @@ -0,0 +1,17 @@ + + + spring-integration-core + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/spring-integration-core/build.xml b/spring-integration-core/build.xml new file mode 100644 index 0000000000..0a554b9e0c --- /dev/null +++ b/spring-integration-core/build.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/spring-integration-core/ivy.xml b/spring-integration-core/ivy.xml new file mode 100644 index 0000000000..267b763389 --- /dev/null +++ b/spring-integration-core/ivy.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-integration-core/resources/changelog.txt b/spring-integration-core/resources/changelog.txt new file mode 100644 index 0000000000..5db0f83d0c --- /dev/null +++ b/spring-integration-core/resources/changelog.txt @@ -0,0 +1,6 @@ +SPRING INTEGRATION CHANGELOG +============================ + +Changes in version 1.0 M1 (Jan 04, 2008) +---------------------------------------- + diff --git a/spring-integration-core/resources/license.txt b/spring-integration-core/resources/license.txt new file mode 100644 index 0000000000..29f81d812f --- /dev/null +++ b/spring-integration-core/resources/license.txt @@ -0,0 +1,201 @@ + 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/spring-integration-core/resources/notice.txt b/spring-integration-core/resources/notice.txt new file mode 100644 index 0000000000..4cb43e0bd3 --- /dev/null +++ b/spring-integration-core/resources/notice.txt @@ -0,0 +1,21 @@ + ======================================================================== + == NOTICE file corresponding to section 4 d of the Apache License, == + == Version 2.0, in this case for the Spring Integration distribution. == + ======================================================================== + + This product includes software developed by + the Apache Software Foundation (http://www.apache.org). + + The end-user documentation included with a redistribution, if any, + must include the following acknowledgement: + + "This product includes software developed by the Spring Framework + Project (http://www.springframework.org)." + + Alternatively, this acknowledgement may appear in the software itself, + if and wherever such third-party acknowledgements normally appear. + + The names "Spring", "Spring Framework", and "Spring Integration" must + not be used to endorse or promote products derived from this software + without prior written permission. For written permission, please contact + rod.johnson@springsource.com or juergen.hoeller@springsource.com. diff --git a/spring-integration-core/resources/readme.txt b/spring-integration-core/resources/readme.txt new file mode 100644 index 0000000000..5440768ba7 --- /dev/null +++ b/spring-integration-core/resources/readme.txt @@ -0,0 +1,3 @@ +SPRING INTEGRATION 1.0 M1 (JAN 04 2008) +--------------------------------------- + diff --git a/spring-integration-core/src/main/java/META-INF/spring.handlers b/spring-integration-core/src/main/java/META-INF/spring.handlers new file mode 100644 index 0000000000..b6de0cb6fe --- /dev/null +++ b/spring-integration-core/src/main/java/META-INF/spring.handlers @@ -0,0 +1 @@ +http\://www.springframework.org/schema/integration=org.springframework.integration.config.IntegrationNamespaceHandler \ No newline at end of file diff --git a/spring-integration-core/src/main/java/META-INF/spring.schemas b/spring-integration-core/src/main/java/META-INF/spring.schemas new file mode 100644 index 0000000000..bc2146688a --- /dev/null +++ b/spring-integration-core/src/main/java/META-INF/spring.schemas @@ -0,0 +1 @@ +http\://www.springframework.org/schema/integration/spring-integration-1.0.xsd=org/springframework/integration/config/spring-integration-1.0.xsd \ No newline at end of file diff --git a/spring-integration-core/src/main/java/org/springframework/integration/MessageDeliveryException.java b/spring-integration-core/src/main/java/org/springframework/integration/MessageDeliveryException.java new file mode 100644 index 0000000000..88175ef687 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/MessageDeliveryException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration; + +/** + * Exception that indicates an error during message delivery. + * + * @author Mark Fisher + */ +@SuppressWarnings("serial") +public class MessageDeliveryException extends MessagingException { + + public MessageDeliveryException(String message) { + super(message); + } + + public MessageDeliveryException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/MessageHandlingException.java b/spring-integration-core/src/main/java/org/springframework/integration/MessageHandlingException.java new file mode 100644 index 0000000000..63d40875a2 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/MessageHandlingException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration; + +/** + * Exception that indicates an error during message handling. + * + * @author Mark Fisher + */ +@SuppressWarnings("serial") +public class MessageHandlingException extends MessagingException { + + public MessageHandlingException(String message) { + super(message); + } + + public MessageHandlingException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/MessagingConfigurationException.java b/spring-integration-core/src/main/java/org/springframework/integration/MessagingConfigurationException.java new file mode 100644 index 0000000000..da834b6bf0 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/MessagingConfigurationException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration; + +/** + * Exception that indicates an incorrectly configured messaging component. + * + * @author Mark Fisher + */ +@SuppressWarnings("serial") +public class MessagingConfigurationException extends MessagingException { + + public MessagingConfigurationException(String message) { + super(message); + } + + public MessagingConfigurationException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/MessagingException.java b/spring-integration-core/src/main/java/org/springframework/integration/MessagingException.java new file mode 100644 index 0000000000..5fc571fe28 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/MessagingException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration; + +/** + * The base exception for any failures within the messaging system. + * + * @author Mark Fisher + */ +@SuppressWarnings("serial") +public class MessagingException extends RuntimeException { + + public MessagingException(String message) { + super(message); + } + + public MessagingException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/SystemInterruptedException.java b/spring-integration-core/src/main/java/org/springframework/integration/SystemInterruptedException.java new file mode 100644 index 0000000000..2d8f485bae --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/SystemInterruptedException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration; + +/** + * An unchecked wrapper exception for InterruptedExceptions. + * + * @author Mark Fisher + */ +@SuppressWarnings("serial") +public class SystemInterruptedException extends MessagingException { + + public SystemInterruptedException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/AbstractSourceAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/AbstractSourceAdapter.java new file mode 100644 index 0000000000..7c18432b23 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/AbstractSourceAdapter.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter; + +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessageMapper; +import org.springframework.integration.message.SimplePayloadMessageMapper; +import org.springframework.util.Assert; + +/** + * A base class providing common behavior for source adapters. + * + * @author Mark Fisher + */ +public class AbstractSourceAdapter implements SourceAdapter { + + private MessageChannel channel; + + private MessageMapper mapper = new SimplePayloadMessageMapper(); + + private long sendTimeout = -1; + + + public void setChannel(MessageChannel channel) { + Assert.notNull(channel, "'channel' must not be null"); + this.channel = channel; + } + + public void setSendTimeout(long sendTimeout) { + this.sendTimeout = sendTimeout; + } + + public void setMessageMapper(MessageMapper mapper) { + Assert.notNull(mapper, "'mapper' must not be null"); + this.mapper = mapper; + } + + protected MessageMapper getMessageMapper() { + return this.mapper; + } + + protected boolean sendToChannel(T object) { + Message message = this.mapper.toMessage(object); + if (this.sendTimeout < 0) { + return this.channel.send(message); + } + return this.channel.send(message, this.sendTimeout); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/AbstractTargetAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/AbstractTargetAdapter.java new file mode 100644 index 0000000000..105684fdca --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/AbstractTargetAdapter.java @@ -0,0 +1,83 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter; + +import org.springframework.integration.bus.ConsumerPolicy; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessageMapper; +import org.springframework.integration.message.SimplePayloadMessageMapper; +import org.springframework.util.Assert; + +/** + * Base class providing common behavior for target adapters. + * + * @author Mark Fisher + */ +public abstract class AbstractTargetAdapter implements TargetAdapter { + + private String name; + + private MessageChannel channel; + + private MessageMapper mapper = new SimplePayloadMessageMapper(); + + private ConsumerPolicy policy = ConsumerPolicy.newPollingPolicy(5); + + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + public void setChannel(MessageChannel channel) { + Assert.notNull(channel, "'channel' must not be null"); + this.channel = channel; + } + + public MessageChannel getChannel() { + return this.channel; + } + + public void setMessageMapper(MessageMapper mapper) { + Assert.notNull(mapper, "'mapper' must not be null"); + this.mapper = mapper; + } + + protected MessageMapper getMessageMapper() { + return this.mapper; + } + + public void setConsumerPolicy(ConsumerPolicy policy) { + Assert.notNull(policy, "'policy' must not be null"); + this.policy = policy; + } + + public ConsumerPolicy getConsumerPolicy() { + return this.policy; + } + + public final void messageReceived(Message message) { + this.sendToTarget(this.mapper.fromMessage(message)); + } + + protected abstract boolean sendToTarget(T object); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/ArgumentListPreparer.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/ArgumentListPreparer.java new file mode 100644 index 0000000000..3dd80ecd4f --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/ArgumentListPreparer.java @@ -0,0 +1,28 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter; + +/** + * A strategy for preparing an argument list from a single source object. + * + * @author Mark Fisher + */ +public interface ArgumentListPreparer { + + Object[] prepare(Object source); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/DefaultTargetAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/DefaultTargetAdapter.java new file mode 100644 index 0000000000..3a487245b3 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/DefaultTargetAdapter.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter; + +import org.springframework.integration.message.MessageMapper; +import org.springframework.util.Assert; + +/** + * Target adapter implementation that delegates to a {@link MessageMapper} + * and then passes the resulting object to the provided {@link Target}. + * + * @author Mark Fisher + */ +public class DefaultTargetAdapter extends AbstractTargetAdapter { + + private Target target; + + + public DefaultTargetAdapter(Target target) { + Assert.notNull(target, "'target' must not be null"); + this.target = target; + } + + public boolean sendToTarget(T object) { + return this.target.send(object); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/MethodInvokingSource.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/MethodInvokingSource.java new file mode 100644 index 0000000000..3da57229da --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/MethodInvokingSource.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collection; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.integration.MessagingConfigurationException; +import org.springframework.integration.util.MethodValidator; +import org.springframework.integration.util.SimpleMethodInvoker; +import org.springframework.util.Assert; + +/** + * A pollable source that invokes a no-argument method so that its return value + * may be sent to a channel. + * + * @author Mark Fisher + */ +public class MethodInvokingSource implements PollableSource, InitializingBean { + + private T object; + + private String method; + + private SimpleMethodInvoker invoker; + + + public void setObject(T object) { + Assert.notNull(object, "'object' must not be null"); + this.object = object; + } + + public void setMethod(String method) { + Assert.notNull(method, "'method' must not be null"); + this.method = method; + } + + public void afterPropertiesSet() { + this.invoker = new SimpleMethodInvoker(this.object, this.method); + this.invoker.setMethodValidator(new MessageReceivingMethodValidator()); + } + + public Collection poll(int limit) { + if (this.invoker == null) { + this.afterPropertiesSet(); + } + return Arrays.asList(this.invoker.invokeMethod(new Object[] {})); + } + + + private static class MessageReceivingMethodValidator implements MethodValidator { + + public void validate(Method method) { + if (method.getReturnType().equals(void.class)) { + throw new MessagingConfigurationException("MethodInvokingSource requires a non-void returning method."); + } + } + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/MethodInvokingTarget.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/MethodInvokingTarget.java new file mode 100644 index 0000000000..73b76baa06 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/MethodInvokingTarget.java @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.integration.util.SimpleMethodInvoker; +import org.springframework.util.Assert; + +/** + * A messaging target that invokes the specified method on the provided object. + * + * @author Mark Fisher + */ +public class MethodInvokingTarget implements Target, InitializingBean { + + private Log logger = LogFactory.getLog(this.getClass()); + + private T object; + + private String method; + + private SimpleMethodInvoker invoker; + + private ArgumentListPreparer argumentListPreparer; + + + public void setObject(T object) { + Assert.notNull(object, "'object' must not be null"); + this.object = object; + } + + public void setMethod(String method) { + Assert.notNull(method, "'method' must not be null"); + this.method = method; + } + + public void setArgumentListPreparer(ArgumentListPreparer argumentListPreparer) { + this.argumentListPreparer = argumentListPreparer; + } + + public void afterPropertiesSet() { + this.invoker = new SimpleMethodInvoker(this.object, this.method); + } + + public boolean send(Object object) { + Object args[] = null; + if (this.argumentListPreparer != null) { + args = this.argumentListPreparer.prepare(object); + } + else { + args = new Object[] { object }; + } + Object result = this.invoker.invokeMethod(args); + if (result != null && logger.isWarnEnabled()) { + logger.warn("ignoring outbound channel adapter's return value"); + } + return true; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/PollableSource.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/PollableSource.java new file mode 100644 index 0000000000..65d240a64f --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/PollableSource.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter; + +import java.util.Collection; + +/** + * Interface for any external data source that can be polled. + * + * @author Mark Fisher + */ +public interface PollableSource { + + Collection poll(int limit); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/PollingSourceAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/PollingSourceAdapter.java new file mode 100644 index 0000000000..6578eec675 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/PollingSourceAdapter.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter; + +import java.util.Collection; + +import org.springframework.integration.MessageHandlingException; +import org.springframework.integration.bus.ConsumerPolicy; +import org.springframework.integration.bus.MessageDispatcher; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.message.MessageMapper; +import org.springframework.util.Assert; + +/** + * A channel adapter that retrieves objects from a {@link PollableSource}, + * delegates to a {@link MessageMapper} to create messages from those objects, + * and then sends the resulting messages to the provided {@link MessageChannel}. + * + * @author Mark Fisher + */ +public class PollingSourceAdapter extends AbstractSourceAdapter implements MessageDispatcher { + + private static int DEFAULT_PERIOD = 1000; + + private PollableSource source; + + private ConsumerPolicy policy = ConsumerPolicy.newPollingPolicy(DEFAULT_PERIOD); + + + public PollingSourceAdapter(PollableSource source) { + Assert.notNull(source, "'source' must not be null"); + this.source = source; + } + + public void setPeriod(int period) { + Assert.isTrue(period > 0, "'period' must be a positive value"); + this.policy.setPeriod(period); + } + + public void setMaxMessagesPerTask(int maxMessagesPerTask) { + Assert.isTrue(maxMessagesPerTask > 0, "'maxMessagesPerTask' must be a positive value"); + this.policy.setMaxMessagesPerTask(maxMessagesPerTask); + } + + public ConsumerPolicy getConsumerPolicy() { + return this.policy; + } + + public int dispatch() { + int messagesProcessed = 0; + int limit = this.policy.getMaxMessagesPerTask(); + Collection results = this.source.poll(limit); + if (results != null) { + if (results.size() > limit) { + throw new MessageHandlingException("source returned too many results, the limit is " + limit); + } + for (T next : results) { + if (this.sendToChannel(next)) { + messagesProcessed++; + } + } + } + return messagesProcessed; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/SourceAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/SourceAdapter.java new file mode 100644 index 0000000000..5fe72ad111 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/SourceAdapter.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter; + +import org.springframework.integration.channel.MessageChannel; + +/** + * Base interface for source adapters. + * + * @author Mark Fisher + */ +public interface SourceAdapter { + + void setChannel(MessageChannel channel); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/Target.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/Target.java new file mode 100644 index 0000000000..8b2c4dae92 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/Target.java @@ -0,0 +1,29 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter; + +/** + * Interface for any external target that may receive data from an outgoing + * channel adapter. + * + * @author Mark Fisher + */ +public interface Target { + + boolean send(T t); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/TargetAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/TargetAdapter.java new file mode 100644 index 0000000000..f9e3ffdab9 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/TargetAdapter.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter; + +import org.springframework.integration.bus.ConsumerPolicy; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.message.MessageReceiver; + +/** + * Base interface for target adapters. + * + * @author Mark Fisher + */ +public interface TargetAdapter extends MessageReceiver { + + String getName(); + + void setChannel(MessageChannel channel); + + ConsumerPolicy getConsumerPolicy(); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/event/ApplicationEventSourceAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/event/ApplicationEventSourceAdapter.java new file mode 100644 index 0000000000..78167c383a --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/event/ApplicationEventSourceAdapter.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter.event; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.integration.adapter.AbstractSourceAdapter; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; + +/** + * A source adapter for passing Spring + * {@link ApplicationEvent ApplicationEvents} within messages. + * + * @author Mark Fisher + */ +public class ApplicationEventSourceAdapter extends AbstractSourceAdapter implements + ApplicationListener { + + private List> eventTypes = new ArrayList>(); + + + /** + * Set the list of event types (classes that extend ApplicationEvent) that + * this adapter should send to the message channel. By default, all event + * types will be sent. + */ + public void setEventTypes(List> eventTypes) { + Assert.notEmpty(eventTypes, "at least one event type is required"); + this.eventTypes = eventTypes; + } + + public void onApplicationEvent(ApplicationEvent event) { + if (CollectionUtils.isEmpty(this.eventTypes)) { + this.sendToChannel(event); + return; + } + for (Class eventType : this.eventTypes) { + if (eventType.isAssignableFrom(event.getClass())) { + this.sendToChannel(event); + return; + } + } + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/event/ApplicationEventTargetAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/event/ApplicationEventTargetAdapter.java new file mode 100644 index 0000000000..ba0c846bb8 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/event/ApplicationEventTargetAdapter.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter.event; + +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.integration.adapter.AbstractTargetAdapter; + +/** + * A target adapter for publishing {@link MessagingEvent MessagingEvents}. The + * {@link MessagingEvent} is a subclass of Spring's {@link ApplicationEvent} + * used by this adapter to wrap any {@link Message} received on its channel. + * + * @author Mark Fisher + */ +public class ApplicationEventTargetAdapter extends AbstractTargetAdapter implements + ApplicationEventPublisherAware { + + private ApplicationEventPublisher applicationEventPublisher; + + + public ApplicationEventTargetAdapter() { + this.setMessageMapper(new MessagingEventMapper()); + } + + + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.applicationEventPublisher = applicationEventPublisher; + } + + @Override + protected boolean sendToTarget(MessagingEvent event) { + this.applicationEventPublisher.publishEvent(event); + return true; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/event/MessagingEvent.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/event/MessagingEvent.java new file mode 100644 index 0000000000..f83d22ca51 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/event/MessagingEvent.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter.event; + +import org.springframework.context.ApplicationEvent; +import org.springframework.integration.message.Message; + +/** + * A subclass of {@link ApplicationEvent} that wraps a {@link Message}. + * + * @author Mark Fisher + */ +public class MessagingEvent extends ApplicationEvent { + + public MessagingEvent(Message message) { + super(message); + } + + public Message getMessage() { + return (Message) this.getSource(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/event/MessagingEventMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/event/MessagingEventMapper.java new file mode 100644 index 0000000000..bb8ebf539c --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/event/MessagingEventMapper.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter.event; + +import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessageMapper; + +/** + * A {@link MessageMapper} implementation for mapping to and from + * {@link MessagingEvent MessagingEvents}. + * + * @author Mark Fisher + */ +public class MessagingEventMapper implements MessageMapper> { + + public MessagingEvent fromMessage(Message message) { + return new MessagingEvent(message); + } + + public Message toMessage(MessagingEvent event) { + return event.getMessage(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/AbstractFileMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/AbstractFileMapper.java new file mode 100644 index 0000000000..8bb5326fee --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/AbstractFileMapper.java @@ -0,0 +1,101 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter.file; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.integration.MessageHandlingException; +import org.springframework.integration.message.AbstractMessageMapper; +import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.message.Message; +import org.springframework.util.Assert; +import org.springframework.util.FileCopyUtils; + +/** + * Base class providing common behavior for file-based message mappers. + * + * @author Mark Fisher + */ +public abstract class AbstractFileMapper extends AbstractMessageMapper { + + protected Log logger = LogFactory.getLog(this.getClass()); + + private File parentDirectory; + + private File backupDirectory; + + private FileNameGenerator fileNameGenerator = new DefaultFileNameGenerator(); + + + public AbstractFileMapper(File parentDirectory) { + this.parentDirectory = parentDirectory; + } + + public void setBackupDirectory(File backupDirectory) { + this.backupDirectory = backupDirectory; + } + + public void setFileNameGenerator(FileNameGenerator fileNameGenerator) { + Assert.notNull(fileNameGenerator, "'fileNameGenerator' must not be null"); + this.fileNameGenerator = fileNameGenerator; + } + + public File fromMessage(Message message) { + try { + File file = new File(parentDirectory, this.fileNameGenerator.generateFileName(message)); + this.writeToFile(file, message.getPayload()); + return file; + } + catch (Exception e) { + throw new MessageHandlingException("failure occurred mapping file to message", e); + } + } + + public Message toMessage(File file) { + try { + T payload = this.readMessagePayload(file); + if (payload == null) { + return null; + } + Message message = new GenericMessage(this.getUidGenerator().generateUid(), payload); + if (this.backupDirectory != null) { + FileWriter writer = new FileWriter(this.backupDirectory.getAbsolutePath() + + File.separator + file.getName()); + FileCopyUtils.copy(new FileReader(file), writer); + } + file.delete(); + return message; + } + catch (Exception e) { + String errorMessage = "failure occurred mapping file to message"; + if (logger.isWarnEnabled()) { + logger.warn(errorMessage, e); + } + throw new MessageHandlingException(errorMessage, e); + } + } + + protected abstract T readMessagePayload(File file) throws Exception; + + protected abstract void writeToFile(File file, T payload) throws Exception; + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/ByteArrayFileMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/ByteArrayFileMapper.java new file mode 100644 index 0000000000..a8aef0b75b --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/ByteArrayFileMapper.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter.file; + +import java.io.File; + +import org.springframework.util.FileCopyUtils; + +/** + * A {@link org.springframework.integration.message.MessageMapper} + * implementation for messages with a byte array payload. + * + * @author Mark Fisher + */ +public class ByteArrayFileMapper extends AbstractFileMapper { + + public ByteArrayFileMapper(File parentDirectory) { + super(parentDirectory); + } + + + @Override + protected byte[] readMessagePayload(File file) throws Exception { + return FileCopyUtils.copyToByteArray(file); + } + + @Override + protected void writeToFile(File file, byte[] payload) throws Exception { + FileCopyUtils.copy(payload, file); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/DefaultFileNameGenerator.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/DefaultFileNameGenerator.java new file mode 100644 index 0000000000..abeb70c3d6 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/DefaultFileNameGenerator.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter.file; + +import org.springframework.integration.message.Message; +import org.springframework.util.StringUtils; + +/** + * Default implementation of the filename generator strategy. Concatenates the + * message id and the current timestamp. + * + * @author Mark Fisher + */ +public class DefaultFileNameGenerator implements FileNameGenerator { + + public String generateFileName(Message message) { + String filenameProperty = message.getHeader().getProperty(FILENAME_PROPERTY_KEY); + return StringUtils.hasText(filenameProperty) ? + filenameProperty : message.getId() + "-" + System.currentTimeMillis() + ".msg"; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/FileNameGenerator.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/FileNameGenerator.java new file mode 100644 index 0000000000..bff13aa640 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/FileNameGenerator.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter.file; + +import org.springframework.integration.message.Message; + +/** + * Strategy interface for generating a file name from a message. + * + * @author Mark Fisher + */ +public interface FileNameGenerator { + + String FILENAME_PROPERTY_KEY = "filename"; + + + String generateFileName(Message message); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/FileSource.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/FileSource.java new file mode 100644 index 0000000000..4c65794dea --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/FileSource.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter.file; + +import java.io.File; +import java.io.FileFilter; +import java.io.FilenameFilter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.springframework.integration.MessageHandlingException; +import org.springframework.integration.adapter.PollableSource; +import org.springframework.util.Assert; + +/** + * A messaging source that polls a directory to retrieve files. + * + * @author Mark Fisher + */ +public class FileSource implements PollableSource { + + private File directory; + + private FileFilter fileFilter; + + private FilenameFilter filenameFilter; + + + public FileSource(File directory) { + Assert.notNull("directory must not be null"); + this.directory = directory; + } + + public void setFileFilter(FileFilter fileFilter) { + this.fileFilter = fileFilter; + } + + public void setFilenameFilter(FilenameFilter filenameFilter) { + this.filenameFilter = filenameFilter; + } + + public Collection poll(int limit) { + File[] files = null; + if (this.fileFilter != null) { + files = this.directory.listFiles(fileFilter); + } + else if (this.filenameFilter != null) { + files = this.directory.listFiles(filenameFilter); + } + else { + files = this.directory.listFiles(); + } + if (files == null) { + throw new MessageHandlingException("Problem occurred while polling for files. " + + "Is '" + directory.getAbsolutePath() + "' a directory?"); + } + int size = Math.min(limit, files.length); + List results = new ArrayList(size); + for (int i = 0; i < size; i++) { + results.add(files[i]); + } + return results; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/FileSourceAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/FileSourceAdapter.java new file mode 100644 index 0000000000..adf36b0827 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/FileSourceAdapter.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter.file; + +import java.io.File; + +import org.springframework.integration.adapter.PollingSourceAdapter; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.message.MessageMapper; +import org.springframework.util.Assert; + +/** + * Channel adapter for polling a directory and creating messages from its files. + * + * @author Mark Fisher + */ +public class FileSourceAdapter extends PollingSourceAdapter { + + public FileSourceAdapter(File directory, MessageChannel channel, int period) { + this(directory, channel, period, true); + } + + public FileSourceAdapter(File directory, MessageChannel channel, int period, boolean isTextBased) { + super(new FileSource(directory)); + this.setChannel(channel); + this.setPeriod(period); + if (isTextBased) { + this.setMessageMapper(new TextFileMapper(directory)); + } + else { + this.setMessageMapper(new ByteArrayFileMapper(directory)); + } + } + + public void setFileNameGenerator(FileNameGenerator fileNameGenerator) { + Assert.notNull(fileNameGenerator, "'fileNameGenerator' must not be null"); + MessageMapper mapper = this.getMessageMapper(); + if (mapper instanceof AbstractFileMapper) { + ((AbstractFileMapper) mapper).setFileNameGenerator(fileNameGenerator); + } + } + + public void setBackupDirectory(File backupDirectory) { + Assert.notNull(backupDirectory, "'backupDirectory' must not be null"); + MessageMapper mapper = this.getMessageMapper(); + if (mapper != null && (mapper instanceof AbstractFileMapper)) { + ((AbstractFileMapper) mapper).setBackupDirectory(backupDirectory); + } + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/FileTargetAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/FileTargetAdapter.java new file mode 100644 index 0000000000..7afead73b1 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/FileTargetAdapter.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter.file; + +import java.io.File; + +import org.springframework.integration.adapter.AbstractTargetAdapter; +import org.springframework.integration.message.MessageMapper; +import org.springframework.util.Assert; + +/** + * A convenience adapter for writing files. The actual file writing occurs in + * the message mapper ({@link TextFileMapper} or {@link ByteArrayFileMapper}). + * + * @author Mark Fisher + */ +public class FileTargetAdapter extends AbstractTargetAdapter { + + public FileTargetAdapter(File directory) { + this(directory, true); + } + + public FileTargetAdapter(File directory, boolean isTextBased) { + if (isTextBased) { + this.setMessageMapper(new TextFileMapper(directory)); + } + else { + this.setMessageMapper(new ByteArrayFileMapper(directory)); + } + } + + public void setFileNameGenerator(FileNameGenerator fileNameGenerator) { + Assert.notNull(fileNameGenerator, "'fileNameGenerator' must not be null"); + MessageMapper mapper = this.getMessageMapper(); + if (mapper instanceof AbstractFileMapper) { + ((AbstractFileMapper) mapper).setFileNameGenerator(fileNameGenerator); + } + } + + @Override + protected boolean sendToTarget(File file) { + return file.exists(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/TextFileMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/TextFileMapper.java new file mode 100644 index 0000000000..d1661f21f5 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/TextFileMapper.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter.file; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; + +import org.springframework.util.FileCopyUtils; + +/** + * A {@link org.springframework.integration.message.MessageMapper} + * implementation for messages with a String payload. + * + * @author Mark Fisher + */ +public class TextFileMapper extends AbstractFileMapper { + + public TextFileMapper(File parentDirectory) { + super(parentDirectory); + } + + + @Override + protected String readMessagePayload(File file) throws Exception { + return FileCopyUtils.copyToString(new FileReader(file)); + } + + @Override + protected void writeToFile(File file, String payload) throws Exception { + FileCopyUtils.copy(payload, new FileWriter(file)); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/config/FileSourceAdapterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/config/FileSourceAdapterParser.java new file mode 100644 index 0000000000..b9ddf2216a --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/config/FileSourceAdapterParser.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter.file.config; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import org.springframework.integration.adapter.file.FileSourceAdapter; + +/** + * Parser for the <file-source/> element. + * + * @author Mark Fisher + */ +public class FileSourceAdapterParser extends AbstractSingleBeanDefinitionParser { + + protected Class getBeanClass(Element element) { + return FileSourceAdapter.class; + } + + protected boolean shouldGenerateId() { + return true; + } + + protected void doParse(Element element, BeanDefinitionBuilder builder) { + String directory = element.getAttribute("directory"); + String channel = element.getAttribute("channel"); + String pollPeriod = element.getAttribute("poll-period"); + builder.addConstructorArg(directory); + builder.addConstructorArgReference(channel); + builder.addConstructorArg(pollPeriod); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/config/FileTargetAdapterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/config/FileTargetAdapterParser.java new file mode 100644 index 0000000000..b430cd0b6b --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/file/config/FileTargetAdapterParser.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter.file.config; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import org.springframework.integration.adapter.file.FileTargetAdapter; + +/** + * Parser for the <file-target/> element. + * + * @author Mark Fisher + */ +public class FileTargetAdapterParser extends AbstractSingleBeanDefinitionParser { + + protected Class getBeanClass(Element element) { + return FileTargetAdapter.class; + } + + protected boolean shouldGenerateId() { + return true; + } + + protected void doParse(Element element, BeanDefinitionBuilder builder) { + String directory = element.getAttribute("directory"); + String channel = element.getAttribute("channel"); + builder.addConstructorArg(directory); + builder.addPropertyReference("channel", channel); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/jms/JmsPollableSource.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/jms/JmsPollableSource.java new file mode 100644 index 0000000000..946584f8c7 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/jms/JmsPollableSource.java @@ -0,0 +1,96 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter.jms; + +import java.util.Arrays; +import java.util.Collection; + +import javax.jms.ConnectionFactory; +import javax.jms.Destination; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.integration.MessagingConfigurationException; +import org.springframework.integration.adapter.PollableSource; +import org.springframework.jms.core.JmsTemplate; + +/** + * A source for receiving JMS Messages with a polling listener. This source is + * only recommended for very low message volume. Otherwise, an event-driven + * option that uses one of Spring's MessageListener containers is highly + * recommended. + * + * @author Mark Fisher + */ +public class JmsPollableSource implements PollableSource, InitializingBean { + + private ConnectionFactory connectionFactory; + + private Destination destination; + + private JmsTemplate jmsTemplate; + + + public JmsPollableSource(JmsTemplate jmsTemplate) { + this.jmsTemplate = jmsTemplate; + } + + public JmsPollableSource(ConnectionFactory connectionFactory, Destination destination) { + this.connectionFactory = connectionFactory; + this.destination = destination; + this.initJmsTemplate(); + } + + /** + * No-arg constructor provided for convenience when configuring with + * setters. Note that the initialization callback will validate. + */ + public JmsPollableSource() { + } + + public void setConnectionFactory(ConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + } + + public void setDestination(Destination destination) { + this.destination = destination; + } + + public void setJmsTemplate(JmsTemplate jmsTemplate) { + this.jmsTemplate = jmsTemplate; + } + + public void afterPropertiesSet() { + if (this.jmsTemplate == null) { + if (this.connectionFactory == null || this.destination == null) { + throw new MessagingConfigurationException("Either a 'jmsTemplate' or " + + "*both* 'connectionFactory' and 'destination' are required."); + } + this.initJmsTemplate(); + } + } + + private void initJmsTemplate() { + this.jmsTemplate = new JmsTemplate(); + this.jmsTemplate.setConnectionFactory(this.connectionFactory); + this.jmsTemplate.setDefaultDestination(this.destination); + } + + public Collection poll(int limit) { + return Arrays.asList(this.jmsTemplate.receiveAndConvert()); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/jms/JmsPollingSourceAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/jms/JmsPollingSourceAdapter.java new file mode 100644 index 0000000000..326b6a9b8b --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/jms/JmsPollingSourceAdapter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter.jms; + +import javax.jms.ConnectionFactory; +import javax.jms.Destination; + +import org.springframework.integration.adapter.PollingSourceAdapter; +import org.springframework.jms.core.JmsTemplate; + +/** + * A convenience adapter that wraps a {@link JmsPollableSource}. + * + * @author Mark Fisher + */ +public class JmsPollingSourceAdapter extends PollingSourceAdapter { + + public JmsPollingSourceAdapter(ConnectionFactory connectionFactory, Destination destination) { + super(new JmsPollableSource(connectionFactory, destination)); + } + + public JmsPollingSourceAdapter(JmsTemplate jmsTemplate) { + super(new JmsPollableSource(jmsTemplate)); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/jms/JmsTargetAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/jms/JmsTargetAdapter.java new file mode 100644 index 0000000000..33ed733ef8 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/jms/JmsTargetAdapter.java @@ -0,0 +1,93 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter.jms; + +import javax.jms.ConnectionFactory; +import javax.jms.Destination; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.integration.MessagingConfigurationException; +import org.springframework.integration.adapter.AbstractTargetAdapter; +import org.springframework.jms.core.JmsTemplate; + +/** + * A target adapter for sending JMS Messages. + * + * @author Mark Fisher + */ +public class JmsTargetAdapter extends AbstractTargetAdapter implements InitializingBean { + + private ConnectionFactory connectionFactory; + + private Destination destination; + + private JmsTemplate jmsTemplate; + + + public JmsTargetAdapter(JmsTemplate jmsTemplate) { + this.jmsTemplate = jmsTemplate; + } + + public JmsTargetAdapter(ConnectionFactory connectionFactory, Destination destination) { + this.connectionFactory = connectionFactory; + this.destination = destination; + this.initJmsTemplate(); + } + + /** + * No-arg constructor provided for convenience when configuring with + * setters. Note that the initialization callback will validate. + */ + public JmsTargetAdapter() { + } + + + public void setConnectionFactory(ConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + } + + public void setDestination(Destination destination) { + this.destination = destination; + } + + public void setJmsTemplate(JmsTemplate jmsTemplate) { + this.jmsTemplate = jmsTemplate; + } + + public void afterPropertiesSet() { + if (this.jmsTemplate == null) { + if (this.connectionFactory == null || this.destination == null) { + throw new MessagingConfigurationException("Either a 'jmsTemplate' or " + + "*both* 'connectionFactory' and 'destination' are required."); + } + this.initJmsTemplate(); + } + } + + private void initJmsTemplate() { + this.jmsTemplate = new JmsTemplate(); + this.jmsTemplate.setConnectionFactory(this.connectionFactory); + this.jmsTemplate.setDefaultDestination(this.destination); + } + + @Override + protected boolean sendToTarget(Object object) { + this.jmsTemplate.convertAndSend(object); + return true; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/jms/config/JmsTargetAdapterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/jms/config/JmsTargetAdapterParser.java new file mode 100644 index 0000000000..2190397258 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/jms/config/JmsTargetAdapterParser.java @@ -0,0 +1,82 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter.jms.config; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import org.springframework.integration.adapter.jms.JmsTargetAdapter; +import org.springframework.util.StringUtils; + +/** + * Parser for the <jms-target/> element. + * + * @author Mark Fisher + */ +public class JmsTargetAdapterParser extends AbstractSingleBeanDefinitionParser { + + private static final String JMS_TEMPLATE_ATTRIBUTE = "jms-template"; + + private static final String JMS_TEMPLATE_PROPERTY = "jmsTemplate"; + + private static final String CONNECTION_FACTORY_ATTRIBUTE = "connection-factory"; + + private static final String CONNECTION_FACTORY_PROPERTY = "connectionFactory"; + + private static final String DESTINATION_ATTRIBUTE = "destination"; + + private static final String DESTINATION_PROPERTY = "destination"; + + private static final String CHANNEL_ATTRIBUTE = "channel"; + + private static final String CHANNEL_PROPERTY = "channel"; + + + protected Class getBeanClass(Element element) { + return JmsTargetAdapter.class; + } + + protected boolean shouldGenerateId() { + return true; + } + + protected void doParse(Element element, BeanDefinitionBuilder builder) { + String jmsTemplate = element.getAttribute(JMS_TEMPLATE_ATTRIBUTE); + String connectionFactory = element.getAttribute(CONNECTION_FACTORY_ATTRIBUTE); + String destination = element.getAttribute(DESTINATION_ATTRIBUTE); + if (StringUtils.hasText(jmsTemplate)) { + if (StringUtils.hasText(connectionFactory) || StringUtils.hasText(destination)) { + throw new BeanCreationException("when providing a 'jms-template' reference, neither " + + "'connection-factory' or 'destination' should be provided."); + } + builder.addPropertyReference(JMS_TEMPLATE_PROPERTY, jmsTemplate); + } + else if (StringUtils.hasText(connectionFactory) && StringUtils.hasText(destination)) { + builder.addPropertyReference(CONNECTION_FACTORY_PROPERTY, connectionFactory); + builder.addPropertyReference(DESTINATION_PROPERTY, destination); + } + else { + throw new BeanCreationException("either a 'jms-template' reference or both " + + "'connection-factory' and 'destination' references must be provided."); + } + String channel = element.getAttribute(CHANNEL_ATTRIBUTE); + builder.addPropertyReference(CHANNEL_PROPERTY, channel); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/DefaultOutput.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/DefaultOutput.java new file mode 100644 index 0000000000..9f4c605373 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/DefaultOutput.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.springframework.integration.message.Message; + +/** + * Indicates that a method is capable of sending messages. The method must + * accept a single parameter that is either a {@link Message} or an Object to + * be passed as a message payload. The enclosing class should be annotated with + * {@link MessageEndpoint @MessageEndpoint}. + * + * @author Mark Fisher + */ +@java.lang.annotation.Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface DefaultOutput { + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Handler.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Handler.java new file mode 100644 index 0000000000..386a73339c --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Handler.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * Indicates that a method is capable of handling a message or message payload. + * The method may only accept a single parameter, and the enclosing class should + * be annotated with {@link MessageEndpoint @MessageEndpoint}. + * + * @author Mark Fisher + */ +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface Handler { + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/MessageEndpoint.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/MessageEndpoint.java new file mode 100644 index 0000000000..0e2f5536b2 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/MessageEndpoint.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.stereotype.Component; + +/** + * Indicates that a class is capable of serving as a message endpoint. + * + * @author Mark Fisher + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Component +public @interface MessageEndpoint { + + String input() default ""; + + String defaultOutput() default ""; + + int pollPeriod() default 0; + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Polled.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Polled.java new file mode 100644 index 0000000000..1917127091 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Polled.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that a method is capable of providing messages. The method must not + * accept any parameters but can return either a single object or collection. + * The enclosing class should be annotated with + * {@link MessageEndpoint @MessageEndpoint}. + * + * @author Mark Fisher + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface Polled { + + int period() default 1000; + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Publisher.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Publisher.java new file mode 100644 index 0000000000..804f3da6e8 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Publisher.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that the method's return value should be published to the specified + * channel. The value will only be published if non-null. + * + * @author Mark Fisher + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface Publisher { + + String channel(); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Router.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Router.java new file mode 100644 index 0000000000..6805971bca --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Router.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that a method is capable of resolving to a channel or channel name + * based on a message, message payload, message attribute, or message property. + * + * @author Mark Fisher + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Handler +public @interface Router { + + /** + * String value representing the name of the property that should be passed + * into the router method. This and 'attribute' should not both be provided. + */ + String property() default ""; + + /** + * String value representing the name of the attribute that should be passed + * into the router method. This and 'property' should not both be provided. + */ + String attribute() default ""; + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitter.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitter.java new file mode 100644 index 0000000000..c2cb6837d6 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Splitter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that a method is capable of splitting a single message or message + * payload to produce multiple messages which are then sent to the channel whose + * name is provided with the 'channel' attribute. + * + * @author Mark Fisher + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Handler +public @interface Splitter { + + String channel(); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Subscriber.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Subscriber.java new file mode 100644 index 0000000000..d2613e79eb --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Subscriber.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that a method-invoking handler adapter should delegate to this + * method. + * + * @author Mark Fisher + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface Subscriber { + + String channel(); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/AnnotationAwareMessagePublishingInterceptor.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/AnnotationAwareMessagePublishingInterceptor.java new file mode 100644 index 0000000000..350e4acf4f --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/AnnotationAwareMessagePublishingInterceptor.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.aop; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.aop.support.AopUtils; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.integration.channel.ChannelRegistry; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.util.Assert; + +/** + * {@link MessagePublishingInterceptor} that resolves the channel from the + * publisher annotation of the invoked method. + * + * @author Mark Fisher + */ +public class AnnotationAwareMessagePublishingInterceptor extends MessagePublishingInterceptor { + + private Class publisherAnnotationType; + + private String channelAttributeName; + + private ChannelRegistry channelRegistry; + + + public AnnotationAwareMessagePublishingInterceptor(Class publisherAnnotationType, + String channelAttributeName, ChannelRegistry channelRegistry) { + Assert.notNull(publisherAnnotationType, "'publisherAnnotationType' must not be null"); + Assert.notNull(channelAttributeName, "'channelAttributeName' must not be null"); + Assert.notNull(channelRegistry, "'channelRegistry' must not be null"); + this.publisherAnnotationType = publisherAnnotationType; + this.channelAttributeName = channelAttributeName; + this.channelRegistry = channelRegistry; + } + + + @Override + protected MessageChannel resolveChannel(MethodInvocation invocation) { + Class targetClass = AopUtils.getTargetClass(invocation.getThis()); + Method method = AopUtils.getMostSpecificMethod(invocation.getMethod(), targetClass); + Annotation annotation = AnnotationUtils.getAnnotation(method, this.publisherAnnotationType); + if (annotation != null) { + String channelName = (String) AnnotationUtils.getValue(annotation, this.channelAttributeName); + if (channelName != null) { + MessageChannel channel = this.channelRegistry.lookupChannel(channelName); + if (channel != null) { + return channel; + } + } + } + return super.resolveChannel(invocation); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java new file mode 100644 index 0000000000..397c9f4ac6 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java @@ -0,0 +1,86 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.aop; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.message.MessageMapper; +import org.springframework.integration.message.SimplePayloadMessageMapper; +import org.springframework.util.Assert; + +/** + * Interceptor that publishes a target method's return value to a channel. + * + * @author Mark Fisher + */ +public class MessagePublishingInterceptor implements MethodInterceptor { + + protected Log logger = LogFactory.getLog(getClass()); + + private MessageMapper mapper = new SimplePayloadMessageMapper(); + + private MessageChannel defaultChannel; + + + public void setDefaultChannel(MessageChannel defaultChannel) { + this.defaultChannel = defaultChannel; + } + + /** + * Specify the {@link MessageMapper} to use when creating a message from the + * return value Object. The default is a {@link SimplePayloadMessageMapper}. + * + * @param mapper the mapper to use + */ + public void setMessageMapper(MessageMapper mapper) { + Assert.notNull(mapper, "mapper must not be null"); + this.mapper = mapper; + } + + /** + * Invoke the target method and publish its return value. + */ + public Object invoke(MethodInvocation invocation) throws Throwable { + Object retval = invocation.proceed(); + if (retval != null) { + MessageChannel channel = this.resolveChannel(invocation); + if (channel == null) { + if (logger.isWarnEnabled()) { + logger.warn("unable to resolve channel for intercepted method '" + + invocation.getMethod().getName() + "'"); + } + } + else { + channel.send(mapper.toMessage(retval)); + } + } + return retval; + } + + /** + * Subclasses may override this method to provide custom behavior. + */ + protected MessageChannel resolveChannel(MethodInvocation invocation) { + return this.defaultChannel; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherAnnotationAdvisor.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherAnnotationAdvisor.java new file mode 100644 index 0000000000..13d09b862f --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherAnnotationAdvisor.java @@ -0,0 +1,69 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.aop; + +import java.lang.annotation.Annotation; + +import org.aopalliance.aop.Advice; + +import org.springframework.aop.Pointcut; +import org.springframework.aop.support.AbstractPointcutAdvisor; +import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; +import org.springframework.integration.annotation.Publisher; +import org.springframework.integration.channel.ChannelRegistry; +import org.springframework.util.Assert; + +/** + * Advisor whose pointcut matches a method annotation and whose advice will + * publish a message to the channel provided by that annotation. + * + * @author Mark Fisher + * @see Publisher + * @see AnnotationAwareMessagePublishingInterceptor + */ +@SuppressWarnings("serial") +public class PublisherAnnotationAdvisor extends AbstractPointcutAdvisor { + + private AnnotationAwareMessagePublishingInterceptor advice; + + private AnnotationMatchingPointcut pointcut; + + + public PublisherAnnotationAdvisor(ChannelRegistry channelRegistry) { + this(Publisher.class, "channel", channelRegistry); + } + + public PublisherAnnotationAdvisor(Class publisherAnnotationType, String channelNameAttribute, + ChannelRegistry channelRegistry) { + Assert.notNull(publisherAnnotationType, "'publisherAnnotationType' must not be null"); + Assert.notNull(channelNameAttribute, "'channelNameAttribute' must not be null"); + Assert.notNull(channelRegistry, "'channelRegistry' must not be null"); + this.pointcut = AnnotationMatchingPointcut.forMethodAnnotation(publisherAnnotationType); + this.advice = new AnnotationAwareMessagePublishingInterceptor(publisherAnnotationType, channelNameAttribute, + channelRegistry); + } + + + public Pointcut getPointcut() { + return this.pointcut; + } + + public Advice getAdvice() { + return this.advice; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/bus/AbstractMessageDispatcher.java b/spring-integration-core/src/main/java/org/springframework/integration/bus/AbstractMessageDispatcher.java new file mode 100644 index 0000000000..68f54b047b --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/bus/AbstractMessageDispatcher.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.bus; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.integration.message.Message; + +/** + * Abstract base class for message dispatchers. Delegates to a + * {@link MessageRetriever} strategy. + * + * @author Mark Fisher + */ +public abstract class AbstractMessageDispatcher implements MessageDispatcher { + + protected Log logger = LogFactory.getLog(this.getClass()); + + private MessageRetriever retriever; + + private List executors = new CopyOnWriteArrayList(); + + + public AbstractMessageDispatcher(MessageRetriever retriever) { + this.retriever = retriever; + } + + + public void addExecutor(MessageReceivingExecutor executor) { + executor.start(); + this.executors.add(executor); + } + + protected List getExecutors() { + return this.executors; + } + + /** + * Retrieves messages and dispatches to the executors. + * + * @return the number of messages processed + */ + public int dispatch() { + int messagesProcessed = 0; + Collection> messages = this.retriever.retrieveMessages(); + if (messages == null) { + return 0; + } + for (Message message : messages) { + if (dispatchMessage(message)) { + messagesProcessed++; + } + } + return messagesProcessed; + } + + + protected abstract boolean dispatchMessage(Message message); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/bus/ChannelPollingMessageRetriever.java b/spring-integration-core/src/main/java/org/springframework/integration/bus/ChannelPollingMessageRetriever.java new file mode 100644 index 0000000000..2dc262b91c --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/bus/ChannelPollingMessageRetriever.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.bus; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.message.Message; + +/** + * Message retriever that polls a {@link MessageChannel}. The number of + * messages retrieved per poll is limited by the 'maxMessagesPerTask' + * property of the provided {@link ConsumerPolicy}, and the timeout for each + * receive call is determined by the policy's 'receiveTimeout' + * property. In general, it is recommended to use a value of 1 for + * 'maxMessagesPerTask' whenever a non-zero timeout is provided. Otherwise the + * retriever may be holding on to available messages while waiting for + * additional messages. + * + * @author Mark Fisher + */ +public class ChannelPollingMessageRetriever implements MessageRetriever { + + private MessageChannel channel; + + private ConsumerPolicy policy; + + + public ChannelPollingMessageRetriever(MessageChannel channel, ConsumerPolicy policy) { + this.channel = channel; + this.policy = policy; + } + + + public Collection> retrieveMessages() { + List> messages = new LinkedList>(); + while (messages.size() < this.policy.getMaxMessagesPerTask()) { + Message message = this.channel.receive(this.policy.getReceiveTimeout()); + if (message == null) { + return messages; + } + messages.add(message); + } + return messages; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/bus/ConsumerPolicy.java b/spring-integration-core/src/main/java/org/springframework/integration/bus/ConsumerPolicy.java new file mode 100644 index 0000000000..3b8e782fc1 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/bus/ConsumerPolicy.java @@ -0,0 +1,170 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.bus; + +import java.util.concurrent.TimeUnit; + +import org.springframework.util.Assert; + +/** + * A container for Message consumer configuration metadata. + * + * @author Mark Fisher + */ +public class ConsumerPolicy { + + private static final int DEFAULT_CONCURRENCY = 1; + + private static final int DEFAULT_MAX_CONCURRENCY = 10; + + private static final int DEFAULT_MAX_MESSAGES_PER_TASK = 1; + + private static final int DEFAULT_REJECTION_LIMIT = 10; + + private static final int DEFAULT_REJECTION_LIMIT_WAIT = 1000; + + private static final long DEFAULT_RECEIVE_TIMEOUT = 1000; + + + private int concurrency = DEFAULT_CONCURRENCY; + + private int maxConcurrency = DEFAULT_MAX_CONCURRENCY; + + private int maxMessagesPerTask = DEFAULT_MAX_MESSAGES_PER_TASK; + + private int rejectionLimit = DEFAULT_REJECTION_LIMIT; + + private int rejectionLimitWait = DEFAULT_REJECTION_LIMIT_WAIT; + + private int initialDelay = 0; + + private int period = 5; + + private TimeUnit timeUnit = TimeUnit.MILLISECONDS; + + private boolean fixedRate = false; + + private long receiveTimeout = DEFAULT_RECEIVE_TIMEOUT; + + + /** + * Factory method for a basic polling policy. + * + * @param period the polling interval + */ + public static ConsumerPolicy newPollingPolicy(int period) { + Assert.isTrue(period > 0, "'period' must be a positive value"); + ConsumerPolicy policy = new ConsumerPolicy(); + policy.setPeriod(period); + policy.setConcurrency(1); + policy.setMaxConcurrency(1); + return policy; + } + + public int getInitialDelay() { + return this.initialDelay; + } + + public void setInitialDelay(int initialDelay) { + this.initialDelay = initialDelay; + } + + public int getPeriod() { + return this.period; + } + + public void setPeriod(int period) { + this.period = period; + } + + public TimeUnit getTimeUnit() { + return this.timeUnit; + } + + public void setTimeUnit(TimeUnit timeUnit) { + this.timeUnit = timeUnit; + } + + public boolean isFixedRate() { + return this.fixedRate; + } + + public void setFixedRate(boolean fixedRate) { + this.fixedRate = fixedRate; + } + + public int getConcurrency() { + return this.concurrency; + } + + public void setConcurrency(int concurrency) { + if (concurrency < 1) { + throw new IllegalArgumentException("'concurrency' value must be at least 1"); + } + this.concurrency = concurrency; + } + + public int getMaxConcurrency() { + return this.maxConcurrency; + } + + public void setMaxConcurrency(int maxConcurrency) { + if (maxConcurrency < 1) { + throw new IllegalArgumentException("'maxConcurrency' value must be at least 1"); + } + this.maxConcurrency = maxConcurrency; + } + + public int getMaxMessagesPerTask() { + return this.maxMessagesPerTask; + } + + public void setMaxMessagesPerTask(int maxMessagesPerTask) { + if (maxMessagesPerTask == 0) { + throw new IllegalArgumentException("'maxMessagesPerTask' must not be 0"); + } + this.maxMessagesPerTask = maxMessagesPerTask; + } + + public int getRejectionLimit() { + return this.rejectionLimit; + } + + public void setRejectionLimit(int rejectionLimit) { + if (rejectionLimit < 1) { + throw new IllegalArgumentException("'idleTaskExecutionLimit' must be at least 1"); + } + this.rejectionLimit = rejectionLimit; + } + + public int getRejectionLimitWait() { + return this.rejectionLimitWait; + } + + public void setRejectionLimitWait(int rejectionLimitWait) { + this.rejectionLimitWait = rejectionLimitWait; + } + + public long getReceiveTimeout() { + return this.receiveTimeout; + } + + public void setReceiveTimeout(long receiveTimeout) { + this.receiveTimeout = receiveTimeout; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/bus/MessageBus.java b/spring-integration-core/src/main/java/org/springframework/integration/bus/MessageBus.java new file mode 100644 index 0000000000..68ec43bb56 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/bus/MessageBus.java @@ -0,0 +1,366 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.bus; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ScheduledThreadPoolExecutor; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.Lifecycle; +import org.springframework.integration.MessagingException; +import org.springframework.integration.adapter.AbstractTargetAdapter; +import org.springframework.integration.adapter.SourceAdapter; +import org.springframework.integration.adapter.TargetAdapter; +import org.springframework.integration.channel.ChannelRegistry; +import org.springframework.integration.channel.DefaultChannelRegistry; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.channel.PointToPointChannel; +import org.springframework.integration.endpoint.MessageEndpoint; +import org.springframework.integration.message.MessageReceiver; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * The messaging bus. Serves as a registry for channels and endpoints, manages their lifecycle, + * and activates subscriptions. + * + * @author Mark Fisher + */ +public class MessageBus implements ChannelRegistry, ApplicationContextAware, Lifecycle { + + private Log logger = LogFactory.getLog(this.getClass()); + + private ChannelRegistry channelRegistry = new DefaultChannelRegistry(); + + private Map> endpoints = new ConcurrentHashMap>(); + + private Map> targetAdapters = new ConcurrentHashMap>(); + + private List dispatcherTasks = new CopyOnWriteArrayList(); + + private Map, MessageReceivingExecutor> receiverExecutors = new ConcurrentHashMap, MessageReceivingExecutor>(); + + private ScheduledThreadPoolExecutor dispatcherExecutor; + + private boolean autoCreateChannels; + + private boolean running; + + private Object lifecycleMonitor = new Object(); + + + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + Assert.notNull(applicationContext, "applicationContext must not be null"); + this.registerChannels(applicationContext); + this.registerEndpoints(applicationContext); + this.registerSourceAdapters(applicationContext); + this.registerTargetAdapters(applicationContext); + this.activateSubscriptions(applicationContext); + } + + public void setAutoCreateChannels(boolean autoCreateChannels) { + this.autoCreateChannels = autoCreateChannels; + } + + @SuppressWarnings("unchecked") + private void registerChannels(ApplicationContext context) { + Map channelBeans = + (Map) context.getBeansOfType(MessageChannel.class); + for (Map.Entry entry : channelBeans.entrySet()) { + this.registerChannel(entry.getKey(), entry.getValue()); + } + } + + @SuppressWarnings("unchecked") + private void registerEndpoints(ApplicationContext context) { + Map endpointBeans = + (Map) context.getBeansOfType(MessageEndpoint.class); + for (Map.Entry entry : endpointBeans.entrySet()) { + this.registerEndpoint(entry.getKey(), entry.getValue()); + } + } + + @SuppressWarnings("unchecked") + private void registerSourceAdapters(ApplicationContext context) { + Map sourceAdapterBeans = + (Map) context.getBeansOfType(SourceAdapter.class); + for (Map.Entry entry : sourceAdapterBeans.entrySet()) { + this.registerSourceAdapter(entry.getKey(), entry.getValue()); + } + } + + @SuppressWarnings("unchecked") + private void registerTargetAdapters(ApplicationContext context) { + Map targetAdapterBeans = + (Map) context.getBeansOfType(TargetAdapter.class); + for (Map.Entry entry : targetAdapterBeans.entrySet()) { + this.registerTargetAdapter(entry.getKey(), entry.getValue()); + } + } + + @SuppressWarnings("unchecked") + private void activateSubscriptions(ApplicationContext context) { + Map subscriptionBeans = + (Map) context.getBeansOfType(Subscription.class); + for (Subscription subscription : subscriptionBeans.values()) { + this.activateSubscription(subscription); + if (logger.isInfoEnabled()) { + logger.info("activated subscription to channel '" + subscription.getChannel() + + "' for endpoint '" + subscription.getEndpoint() + "'"); + } + } + } + + public void initialize() { + this.dispatcherExecutor = new ScheduledThreadPoolExecutor(this.dispatcherTasks.size() > 0 ? this.dispatcherTasks.size() : 1); + } + + public MessageChannel getInvalidMessageChannel() { + return this.channelRegistry.getInvalidMessageChannel(); + } + + public void setInvalidMessageChannel(MessageChannel invalidMessageChannel) { + this.channelRegistry.setInvalidMessageChannel(invalidMessageChannel); + } + + public MessageChannel lookupChannel(String channelName) { + return this.channelRegistry.lookupChannel(channelName); + } + + public void registerChannel(String name, MessageChannel channel) { + this.channelRegistry.registerChannel(name, channel); + } + + public void registerEndpoint(String name, MessageEndpoint endpoint) { + Assert.notNull(name, "'name' must not be null"); + Assert.notNull(endpoint, "'endpoint' must not be null"); + this.endpoints.put(name, endpoint); + if (logger.isInfoEnabled()) { + logger.info("registered endpoint '" + name + "'"); + } + endpoint.setChannelRegistry(this); + if (endpoint.getInputChannelName() != null && endpoint.getConsumerPolicy() != null) { + Subscription subscription = new Subscription(); + subscription.setChannel(endpoint.getInputChannelName()); + subscription.setEndpoint(name); + subscription.setPolicy(endpoint.getConsumerPolicy()); + this.activateSubscription(subscription); + } + if (this.autoCreateChannels) { + String defaultOutputChannelName = endpoint.getDefaultOutputChannelName(); + if (StringUtils.hasText(defaultOutputChannelName) && this.lookupChannel(defaultOutputChannelName) == null) { + this.registerChannel(defaultOutputChannelName, new PointToPointChannel()); + } + } + } + + public void registerSourceAdapter(String name, SourceAdapter adapter) { + // TODO: use the name + if (adapter instanceof MessageDispatcher) { + MessageDispatcher dispatcher = (MessageDispatcher) adapter; + ConsumerPolicy policy = dispatcher.getConsumerPolicy(); + DispatcherTask dispatcherTask = new DispatcherTask(dispatcher, policy); + this.addDispatcherTask(dispatcherTask); + if (logger.isInfoEnabled()) { + logger.info("registered source adapter '" + name + "'"); + } + } + } + + public void registerTargetAdapter(String name, TargetAdapter targetAdapter) { + if (targetAdapter instanceof AbstractTargetAdapter) { + AbstractTargetAdapter adapter = (AbstractTargetAdapter) targetAdapter; + adapter.setName(name); + this.targetAdapters.put(name, targetAdapter); + MessageChannel channel = adapter.getChannel(); + ConsumerPolicy policy = adapter.getConsumerPolicy(); + MessageRetriever retriever = new ChannelPollingMessageRetriever(channel, policy); + UnicastMessageDispatcher dispatcher = new UnicastMessageDispatcher(retriever, policy); + dispatcher.addExecutor(new MessageReceivingExecutor(adapter, policy.getConcurrency(), policy.getMaxConcurrency())); + this.addDispatcherTask(new DispatcherTask(dispatcher, policy)); + if (logger.isInfoEnabled()) { + logger.info("registered target adapter '" + name + "'"); + } + } + } + + public void activateSubscription(Subscription subscription) { + String channelName = subscription.getChannel(); + String endpointName = subscription.getEndpoint(); + ConsumerPolicy policy = subscription.getPolicy(); + MessageChannel channel = this.lookupChannel(channelName); + if (channel == null) { + if (this.autoCreateChannels == false) { + throw new MessagingException("Cannot activate subscription, unknown channel '" + channelName + + "'. Consider enabling the 'autoCreateChannels' option for the message bus."); + } + if (this.logger.isInfoEnabled()) { + logger.info("auto-creating channel '" + channelName + "'"); + } + channel = new PointToPointChannel(); + this.registerChannel(channelName, channel); + } + MessageEndpoint endpoint = this.endpoints.get(endpointName); + if (endpoint == null) { + throw new MessagingException("Cannot activate subscription, unknown endpoint '" + endpointName + "'"); + } + if (logger.isInfoEnabled()) { + logger.info("activated subscription to channel '" + channelName + + "' for endpoint '" + endpointName + "'"); + } + MessageReceivingExecutor executor = new MessageReceivingExecutor(endpoint, policy.getConcurrency(), policy.getMaxConcurrency()); + receiverExecutors.put(endpoint, executor); + MessageRetriever retriever = new ChannelPollingMessageRetriever(channel, policy); + UnicastMessageDispatcher dispatcher = new UnicastMessageDispatcher(retriever, policy); + dispatcher.addExecutor(executor); + DispatcherTask dispatcherTask = new DispatcherTask(dispatcher, policy); + if (this.isRunning()) { + executor.start(); + } + this.addDispatcherTask(dispatcherTask); + if (this.logger.isInfoEnabled()) { + logger.info("registered dispatcher task: channel='" + + channelName + "' receiver='" + endpointName + "'"); + } + } + + private void addDispatcherTask(DispatcherTask dispatcherTask) { + this.dispatcherTasks.add(dispatcherTask); + if (this.isRunning()) { + scheduleDispatcherTask(dispatcherTask); + if (this.logger.isInfoEnabled()) { + logger.info("scheduled dispatcher task"); + } + } + } + + public int getActiveCountForReceiver(String receiverName) { + MessageReceiver receiver = this.endpoints.get(receiverName); + if (receiver == null) { + receiver = this.targetAdapters.get(receiverName); + } + if (receiver != null) { + MessageReceivingExecutor executor = this.receiverExecutors.get(receiver); + if (executor != null) { + return executor.getActiveCount(); + } + } + return 0; + } + + private void scheduleDispatcherTask(DispatcherTask task) { + ConsumerPolicy policy = task.getPolicy(); + if (policy.getPeriod() <= 0) { + if (policy.getReceiveTimeout() <= 0) { + if (logger.isWarnEnabled()) { + logger.warn("Scheduling a repeating task with no receive timeout is not recommended! " + + "Consider providing a positive value for either 'period' or 'receiveTimeout'"); + } + } + dispatcherExecutor.schedule(new RepeatingDispatcherTask(task), policy.getInitialDelay(), policy.getTimeUnit()); + } + else if (policy.isFixedRate()) { + dispatcherExecutor.scheduleAtFixedRate(task, policy.getInitialDelay(), policy.getPeriod(), policy.getTimeUnit()); + } + else { + dispatcherExecutor.scheduleWithFixedDelay(task, policy.getInitialDelay(), policy.getPeriod(), policy.getTimeUnit()); + } + } + + public boolean isRunning() { + synchronized (this.lifecycleMonitor) { + return this.running; + } + } + + public void start() { + if (this.dispatcherExecutor == null) { + this.initialize(); + } + synchronized (this.lifecycleMonitor) { + if (!this.isRunning()) { + this.running = true; + for (MessageReceivingExecutor executor : receiverExecutors.values()) { + executor.start(); + } + for (DispatcherTask task : this.dispatcherTasks) { + scheduleDispatcherTask(task); + } + } + } + } + + public void stop() { + synchronized (this.lifecycleMonitor) { + if (this.isRunning()) { + this.running = false; + for (MessageReceivingExecutor executor : receiverExecutors.values()) { + executor.stop(); + } + this.dispatcherExecutor.shutdownNow(); + } + } + } + + + private static class DispatcherTask implements Runnable { + + private MessageDispatcher dispatcher; + + private ConsumerPolicy policy; + + + public DispatcherTask(MessageDispatcher dispatcher, ConsumerPolicy policy) { + this.dispatcher = dispatcher; + this.policy = policy; + } + + + public ConsumerPolicy getPolicy() { + return this.policy; + } + + public void run() { + dispatcher.dispatch(); + } + + } + + + private class RepeatingDispatcherTask implements Runnable { + + private DispatcherTask task; + + RepeatingDispatcherTask(DispatcherTask task) { + this.task = task; + } + + public void run() { + task.run(); + dispatcherExecutor.execute(new RepeatingDispatcherTask(task)); + } + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/bus/MessageDispatcher.java b/spring-integration-core/src/main/java/org/springframework/integration/bus/MessageDispatcher.java new file mode 100644 index 0000000000..e350f83aa8 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/bus/MessageDispatcher.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.bus; + +/** + * Strategy interface for dispatching messages. + * + * @author Mark Fisher + */ +public interface MessageDispatcher { + + ConsumerPolicy getConsumerPolicy(); + + int dispatch(); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/bus/MessageReceivingExecutor.java b/spring-integration-core/src/main/java/org/springframework/integration/bus/MessageReceivingExecutor.java new file mode 100644 index 0000000000..4a42a0557c --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/bus/MessageReceivingExecutor.java @@ -0,0 +1,201 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.bus; + +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.context.Lifecycle; +import org.springframework.integration.MessageHandlingException; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessageReceiver; +import org.springframework.scheduling.concurrent.CustomizableThreadFactory; +import org.springframework.util.Assert; + +/** + * Encapsulates a {@link ThreadPoolExecutor} with configurable error thresholds. + * + * @author Mark Fisher + */ +public class MessageReceivingExecutor implements Lifecycle { + + private Log logger = LogFactory.getLog(this.getClass()); + + private MessageReceiver receiver; + + private ThreadPoolExecutor threadPoolExecutor; + + private int corePoolSize; + + private int maxPoolSize; + + private volatile boolean running; + + private Object lifecycleMonitor = new Object(); + + private int successiveErrorCount; + + private int successiveErrorThreshold = -1; + + private int totalErrorCount; + + private int totalErrorThreshold = -1; + + + public MessageReceivingExecutor(MessageReceiver receiver, int corePoolSize, int maxPoolSize) { + Assert.notNull(receiver, "'receiver' must not be null"); + Assert.isTrue(corePoolSize > 0, "'corePoolSize' must be at least 1"); + Assert.isTrue(maxPoolSize > 0, "'maxPoolSize' must be at least 1"); + Assert.isTrue(maxPoolSize >= corePoolSize, "'corePoolSize' cannot exceed 'maxPoolSize'"); + this.receiver = receiver; + this.corePoolSize = corePoolSize; + this.maxPoolSize = maxPoolSize; + } + + public void setCorePoolSize(int corePoolSize) { + this.corePoolSize = corePoolSize; + } + + public void setMaxPoolSize(int maxPoolSize) { + this.maxPoolSize = maxPoolSize; + } + + public boolean isRunning() { + return this.running; + } + + public void start() { + synchronized (this.lifecycleMonitor) { + if (!this.running) { + this.threadPoolExecutor = new MessageReceivingThreadPoolExecutor(this.corePoolSize, this.maxPoolSize); + } + this.running = true; + } + } + + public void stop() { + synchronized (this.lifecycleMonitor) { + if (this.isRunning()) { + this.threadPoolExecutor.shutdown(); + this.threadPoolExecutor = null; + } + this.running = false; + } + } + + public void processMessage(Message message) { + if (threadPoolExecutor == null) { + throw new MessageHandlingException("executor is not running"); + } + this.threadPoolExecutor.execute(new MessageReceivingTask(this.receiver, message)); + } + + /** + * Set the maximum number of errors allowed in successive + * executions. If this threshold is ever exceeded, the executor + * will shutdown. + */ + public void setSuccessiveErrorThreshold(int successiveErrorThreshold) { + this.successiveErrorThreshold = successiveErrorThreshold; + } + + /** + * Set the maximum number of total errors allowed in executions + * If this threshold is ever exceeded, the executor will shutdown. + */ + public void setTotalErrorThreshold(int totalErrorThreshold) { + this.totalErrorThreshold = totalErrorThreshold; + } + + public int getActiveCount() { + if (this.threadPoolExecutor == null) { + return 0; + } + return this.threadPoolExecutor.getActiveCount(); + } + + public boolean isShutdown() { + return this.threadPoolExecutor.isShutdown(); + } + + + private static class MessageReceivingTask implements Runnable { + + private MessageReceiver receiver; + + private Message message; + + private Throwable error; + + + MessageReceivingTask(MessageReceiver receiver, Message message) { + this.receiver = receiver; + this.message = message; + } + + public Throwable getError() { + return this.error; + } + + public void run() { + try { + this.receiver.messageReceived(this.message); + } + catch (Throwable t) { + this.error = t; + } + } + } + + + private class MessageReceivingThreadPoolExecutor extends ThreadPoolExecutor { + + public MessageReceivingThreadPoolExecutor(int corePoolSize, int maximumPoolSize) { + super(corePoolSize, maximumPoolSize, 0, TimeUnit.MILLISECONDS, new SynchronousQueue()); + CustomizableThreadFactory threadFactory = new CustomizableThreadFactory(); + threadFactory.setThreadNamePrefix("endpoint-executor-"); + this.setThreadFactory(threadFactory); + } + + @Override + protected void afterExecute(Runnable r, Throwable t) { + MessageReceivingTask task = (MessageReceivingTask) r; + if (task.getError() != null) { + if (logger.isWarnEnabled()) { + logger.warn("Exception occurred during task execution", task.getError()); + } + successiveErrorCount++; + totalErrorCount++; + if ((successiveErrorThreshold >= 0 && successiveErrorCount > successiveErrorThreshold) + || (totalErrorThreshold >= 0 && totalErrorCount > totalErrorThreshold)) { + if (logger.isInfoEnabled()) { + logger.info("error threshold exceeded, shutting down now"); + } + this.shutdownNow(); + } + } + else { + successiveErrorCount = 0; + } + } + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/bus/MessageRetriever.java b/spring-integration-core/src/main/java/org/springframework/integration/bus/MessageRetriever.java new file mode 100644 index 0000000000..f728cdd3a9 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/bus/MessageRetriever.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.bus; + +import java.util.Collection; + +import org.springframework.integration.message.Message; + +/** + * Strategy interface for retrieving messages. + * + * @author Mark Fisher + */ +public interface MessageRetriever { + + Collection> retrieveMessages(); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/bus/Subscription.java b/spring-integration-core/src/main/java/org/springframework/integration/bus/Subscription.java new file mode 100644 index 0000000000..e11e2977c1 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/bus/Subscription.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.bus; + +/** + * Configuration metadata for activating a subscription. + * + * @author Mark Fisher + */ +public class Subscription { + + private String channel; + + private String endpoint; + + private ConsumerPolicy policy = new ConsumerPolicy(); + + + public String getChannel() { + return this.channel; + } + + public void setChannel(String channel) { + this.channel = channel; + } + + public String getEndpoint() { + return this.endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public ConsumerPolicy getPolicy() { + return this.policy; + } + + public void setPolicy(ConsumerPolicy policy) { + this.policy = policy; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/bus/UnicastMessageDispatcher.java b/spring-integration-core/src/main/java/org/springframework/integration/bus/UnicastMessageDispatcher.java new file mode 100644 index 0000000000..e5a721f310 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/bus/UnicastMessageDispatcher.java @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.bus; + +import java.util.Iterator; +import java.util.concurrent.RejectedExecutionException; + +import org.springframework.integration.message.Message; + +/** + * A {@link MessageDispatcher} implementation that dispatches each retrieved + * {@link Message} to a single {@link MessageReceivingExecutor}. + * + * @author Mark Fisher + */ +public class UnicastMessageDispatcher extends AbstractMessageDispatcher { + + private ConsumerPolicy policy; + + + public UnicastMessageDispatcher(MessageRetriever retriever, ConsumerPolicy policy) { + super(retriever); + this.policy = policy; + } + + + public ConsumerPolicy getConsumerPolicy() { + return this.policy; + } + + @Override + protected boolean dispatchMessage(Message message) { + int attempts = 0; + Iterator iter = this.getExecutors().iterator(); + if (!iter.hasNext()) { + if (logger.isWarnEnabled()) { + logger.warn("dispatcher has no active executors"); + } + return false; + } + while (iter.hasNext()) { + MessageReceivingExecutor executor = iter.next(); + try { + if (executor == null || !executor.isRunning()) { + if (logger.isInfoEnabled()) { + logger.info("removing inactive executor"); + } + iter.remove(); + continue; + } + executor.processMessage(message); + return true; + } + catch (RejectedExecutionException rex) { + attempts++; + if (attempts == policy.getRejectionLimit()) { + attempts = 0; + if (logger.isDebugEnabled()) { + logger.debug("reached rejected execution limit"); + } + try { + Thread.sleep(policy.getRejectionLimitWait()); + } + catch (InterruptedException iex) { + Thread.currentThread().interrupt(); + } + } + } + catch (Exception e) { + if (logger.isWarnEnabled()) { + logger.warn("error occurred during dispatch", e); + } + } + } + return false; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/ChannelRegistry.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/ChannelRegistry.java new file mode 100644 index 0000000000..0217c5a98a --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/channel/ChannelRegistry.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.channel; + +/** + * A strategy interface for registration and lookup of message channels by name. + * + * @author Mark Fisher + */ +public interface ChannelRegistry { + + void registerChannel(String name, MessageChannel channel); + + MessageChannel lookupChannel(String channelName); + + void setInvalidMessageChannel(MessageChannel invalidMessageChannel); + + MessageChannel getInvalidMessageChannel(); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/ChannelRegistryAware.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/ChannelRegistryAware.java new file mode 100644 index 0000000000..267538535b --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/channel/ChannelRegistryAware.java @@ -0,0 +1,29 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.channel; + +/** + * Interface to be implemented by components that need access to the + * {@link ChannelRegistry}. + * + * @author Mark Fisher + */ +public interface ChannelRegistryAware { + + void setChannelRegistry(ChannelRegistry channelRegistry); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/ConsumerType.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/ConsumerType.java new file mode 100644 index 0000000000..c0e1a3388b --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/channel/ConsumerType.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.channel; + +/** + * Enumeration of the different types of message consumer. + * + * @author Mark Fisher + */ +public enum ConsumerType { + + EVENT_DRIVEN, + + FIXED_RATE, + + FIXED_DELAY, + + SCHEDULED + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/DefaultChannelRegistry.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/DefaultChannelRegistry.java new file mode 100644 index 0000000000..4fdbd33996 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/channel/DefaultChannelRegistry.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.channel; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.util.Assert; + +/** + * A simple map-backed implementation of {@link ChannelRegistry}. + * + * @author Mark Fisher + */ +public class DefaultChannelRegistry implements ChannelRegistry { + + private Log logger = LogFactory.getLog(this.getClass()); + + private Map channels = new ConcurrentHashMap(); + + private MessageChannel invalidMessageChannel; + + + public void setInvalidMessageChannel(MessageChannel invalidMessageChannel) { + this.invalidMessageChannel = invalidMessageChannel; + } + + public MessageChannel getInvalidMessageChannel() { + return this.invalidMessageChannel; + } + + public MessageChannel lookupChannel(String channelName) { + return this.channels.get(channelName); + } + + public void registerChannel(String name, MessageChannel channel) { + Assert.notNull(name, "'name' must not be null"); + Assert.notNull(channel, "'channel' must not be null"); + channel.setName(name); + this.channels.put(name, channel); + if (logger.isInfoEnabled()) { + logger.info("registered channel '" + name + "'"); + } + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/MessageChannel.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/MessageChannel.java new file mode 100644 index 0000000000..25482e11f7 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/channel/MessageChannel.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.channel; + +import org.springframework.integration.message.Message; + +/** + * Base channel interface defining common behavior for message sending and receiving. + * + * @author Mark Fisher + */ +public interface MessageChannel { + + /** + * Set the name of this channel. Must be unique within a context. + */ + void setName(String name); + + /** + * Return the name of this channel. + */ + String getName(); + + /** + * Send a message, blocking indefinitely if necessary. + * + * @param message the {@link Message} to send + * + * @return true if the message is sent successfully, + * false if interrupted + */ + boolean send(Message message); + + /** + * Send a message, blocking until either the message is accepted or the + * specified timeout period elapses. + * + * @param message the {@link Message} to send + * @param timeout the timeout in milliseconds + * + * @return true if the message is sent successfully, + * false if the specified timeout period elapses or + * the send is interrupted + */ + boolean send(Message message, long timeout); + + /** + * Receive a message, blocking indefinitely if necessary. + * + * @return the next available {@link Message} or null if + * interrupted + */ + Message receive(); + + /** + * Receive a message, blocking until either a message is available or the + * specified timeout period elapses. + * + * @param timeout the timeout in milliseconds + * + * @return the next available {@link Message} or null if the + * specified timeout period elapses or the message reception is interrupted + */ + Message receive(long timeout); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/PointToPointChannel.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/PointToPointChannel.java new file mode 100644 index 0000000000..e89caf7100 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/channel/PointToPointChannel.java @@ -0,0 +1,213 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.channel; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessageSelector; + +/** + * Simple implementation of a point-to-point message channel. Each Messages is + * placed in a queue whose capacity may be specified upon construction. If no + * capacity is specified, the {@link #DEFAULT_CAPACITY} will be used. + * + * @author Mark Fisher + */ +public class PointToPointChannel implements MessageChannel, BeanNameAware { + + private static final int DEFAULT_CAPACITY = 25; + + private String name; + + private BlockingQueue> queue; + + /** + * Create a channel with the specified queue capacity. + */ + public PointToPointChannel(int capacity) { + queue = new LinkedBlockingQueue>(capacity); + } + + /** + * Create a channel with the default queue capacity. + */ + public PointToPointChannel() { + this(DEFAULT_CAPACITY); + } + + + /** + * Set the name of this channel. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Return the name of this channel. + */ + public String getName() { + return this.name; + } + + /** + * Set the name of this channel to its bean name. This will be invoked + * automatically whenever the channel is configured explicitly with a bean + * definition. + */ + public void setBeanName(String beanName) { + this.setName(beanName); + } + + /** + * Send a message on this channel. If the queue is full, this method will + * block until either space becomes available or the sending thread is + * interrupted. + * + * @param message the Message to send + * + * @return true if the message is sent successfully or + * false if the sending thread is interrupted. + */ + public boolean send(Message message) { + try { + queue.put(message); + return true; + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } + } + + /** + * Send a message on this channel. If the queue is full, this method will + * block until either the timeout occurs or the sending thread is + * interrupted. If the specified timeout is less than 1, the method will + * return immediately. + * + * @param message the Message to send + * @param timeout the timeout in milliseconds + * + * @return true if the message is sent successfully, + * false if the message cannot be sent within the allotted + * time or the sending thread is interrupted. + */ + public boolean send(Message message, long timeout) { + try { + if (timeout > 0) { + return this.queue.offer(message, timeout, TimeUnit.MILLISECONDS); + } + return this.queue.offer(message); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } + } + + /** + * Receive the message at the head of the queue. If the queue is empty, this + * method will block. + * + * @return the Message at the head of the queue or null if + * the receiving thread is interrupted. + */ + public Message receive() { + try { + return queue.take(); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return null; + } + } + + /** + * Receive the message at the head of the queue. If the queue is empty, this + * method will block until the allotted timeout elapses. If the specified + * timeout is less than 1, the method will return immediately. + * + * @param timeout the timeout in milliseconds + * + * @return the message at the head of the queue or null in + * case no message is available within the allotted time or the receiving + * thread is interrupted. + */ + public Message receive(long timeout) { + try { + if (timeout > 0) { + return queue.poll(timeout, TimeUnit.MILLISECONDS); + } + return queue.poll(); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return null; + } + } + + /** + * Receive the first message that is accepted by the specified selector + * starting from the head of the queue. If the queue is empty, this method + * will block until the allotted timeout elapses. If the specified timeout + * is less than 1, the method will return immediately. + * + * @param selector the selector to use + * @param timeout the timeout in milliseconds + * + * @return the first accepted message or null in case the + * selector does not accept any message within the allotted time or the + * receiving thread is interrupted. + */ + public Message receive(MessageSelector selector, long timeout) { + long start = System.currentTimeMillis(); + while (timeout <= 0 || System.currentTimeMillis() - start < timeout) { + Object[] elements = this.queue.toArray(); + for (int i = (elements.length - 1); i >= 0; i--) { + Message m = (Message) elements[i]; + if (selector.accept(m) && this.queue.remove(m)) { + return m; + } + } + if (timeout == 0) { + return null; + } + } + return null; + } + + /** + * Receive the first message that is accepted by the specified selector + * starting from the head of the queue. If the queue is empty, this method + * will block. + * + * @param selector the selector to use + * + * @return the first accepted message or null in case the + * selector does not accept any message or the receiving thread is + * interrupted. + */ + public Message receive(MessageSelector selector) { + return this.receive(selector, -1); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/AnnotationDrivenParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/AnnotationDrivenParser.java new file mode 100644 index 0000000000..b6fcc4770f --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/AnnotationDrivenParser.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.config; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; + +/** + * Parser for the annotation-driven element of the integration + * namespace. Registers the annotation-driven post-processors. + * + * @author Mark Fisher + */ +public class AnnotationDrivenParser implements BeanDefinitionParser { + + private static final String PUBLISHER_ANNOTATION_POST_PROCESSOR_BEAN_NAME = + "internal.PublisherAnnotationPostProcessor"; + + private static final String SUBSCRIBER_ANNOTATION_POST_PROCESSOR_BEAN_NAME = + "internal.SubscriberAnnotationPostProcessor"; + + private static final String MESSAGE_ENDPOINT_ANNOTATION_POST_PROCESSOR_BEAN_NAME = + "internal.MessageEndpointAnnotationPostProcessor"; + + + public BeanDefinition parse(Element element, ParserContext parserContext) { + this.createPublisherPostProcessor(parserContext); + this.createSubscriberPostProcessor(parserContext); + this.createMessageEndpointPostProcessor(parserContext); + return null; + } + + private void createPublisherPostProcessor(ParserContext parserContext) { + BeanDefinition bd = new RootBeanDefinition(PublisherAnnotationPostProcessor.class); + bd.getPropertyValues().addPropertyValue("channelRegistry", + new RuntimeBeanReference(MessageBusParser.MESSAGE_BUS_BEAN_NAME)); + BeanComponentDefinition bcd = new BeanComponentDefinition( + bd, PUBLISHER_ANNOTATION_POST_PROCESSOR_BEAN_NAME); + parserContext.registerBeanComponent(bcd); + } + + private void createSubscriberPostProcessor(ParserContext parserContext) { + BeanDefinition bd = new RootBeanDefinition(SubscriberAnnotationPostProcessor.class); + bd.getPropertyValues().addPropertyValue("messageBus", + new RuntimeBeanReference(MessageBusParser.MESSAGE_BUS_BEAN_NAME)); + BeanComponentDefinition bcd = new BeanComponentDefinition( + bd, SUBSCRIBER_ANNOTATION_POST_PROCESSOR_BEAN_NAME); + parserContext.registerBeanComponent(bcd); + } + + private void createMessageEndpointPostProcessor(ParserContext parserContext) { + BeanDefinition bd = new RootBeanDefinition(MessageEndpointAnnotationPostProcessor.class); + bd.getPropertyValues().addPropertyValue("messageBus", + new RuntimeBeanReference(MessageBusParser.MESSAGE_BUS_BEAN_NAME)); + BeanComponentDefinition bcd = new BeanComponentDefinition( + bd, MESSAGE_ENDPOINT_ANNOTATION_POST_PROCESSOR_BEAN_NAME); + parserContext.registerBeanComponent(bcd); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/ChannelAdapterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/ChannelAdapterParser.java new file mode 100644 index 0000000000..2832700001 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/ChannelAdapterParser.java @@ -0,0 +1,102 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.config; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.MessagingConfigurationException; +import org.springframework.integration.adapter.DefaultTargetAdapter; +import org.springframework.integration.adapter.MethodInvokingSource; +import org.springframework.integration.adapter.MethodInvokingTarget; +import org.springframework.integration.adapter.PollingSourceAdapter; +import org.springframework.util.StringUtils; + +/** + * Parser for inbound and outbound channel adapters. + * + * @author Mark Fisher + */ +public class ChannelAdapterParser implements BeanDefinitionParser { + + private static final String ID_ATTRIBUTE = "id"; + + private static final String REF_ATTRIBUTE = "ref"; + + private static final String METHOD_ATTRIBUTE = "method"; + + private static final String CHANNEL_ATTRIBUTE = "channel"; + + private static final String PERIOD_ATTRIBUTE = "period"; + + + private final boolean isInbound; + + + public ChannelAdapterParser(boolean isInbound) { + this.isInbound = isInbound; + } + + + public BeanDefinition parse(Element element, ParserContext parserContext) { + String ref = element.getAttribute(REF_ATTRIBUTE); + String method = element.getAttribute(METHOD_ATTRIBUTE); + String channel = element.getAttribute(CHANNEL_ATTRIBUTE); + if (!StringUtils.hasText(ref)) { + throw new MessagingConfigurationException("'ref' is required"); + } + if (!StringUtils.hasText(method)) { + throw new MessagingConfigurationException("'method' is required"); + } + if (!StringUtils.hasText(channel)) { + throw new MessagingConfigurationException("'channel' is required"); + } + RootBeanDefinition adapterDef = null; + RootBeanDefinition invokerDef = null; + if (this.isInbound) { + adapterDef = new RootBeanDefinition(PollingSourceAdapter.class); + invokerDef = new RootBeanDefinition(MethodInvokingSource.class); + String period = element.getAttribute(PERIOD_ATTRIBUTE); + if (StringUtils.hasText(period)) { + adapterDef.getPropertyValues().addPropertyValue("period", period); + } + } + else { + adapterDef = new RootBeanDefinition(DefaultTargetAdapter.class); + invokerDef = new RootBeanDefinition(MethodInvokingTarget.class); + } + invokerDef.getPropertyValues().addPropertyValue("object", new RuntimeBeanReference(ref)); + invokerDef.getPropertyValues().addPropertyValue("method", method); + String invokerBeanName = parserContext.getReaderContext().generateBeanName(invokerDef); + parserContext.registerBeanComponent(new BeanComponentDefinition(invokerDef, invokerBeanName)); + adapterDef.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference(invokerBeanName)); + adapterDef.getPropertyValues().addPropertyValue("channel", new RuntimeBeanReference(channel)); + adapterDef.setSource(parserContext.extractSource(element)); + String beanName = element.getAttribute(ID_ATTRIBUTE); + if (!StringUtils.hasText(beanName)) { + beanName = parserContext.getReaderContext().generateBeanName(adapterDef); + } + parserContext.registerBeanComponent(new BeanComponentDefinition(adapterDef, beanName)); + return adapterDef; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/ChannelParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/ChannelParser.java new file mode 100644 index 0000000000..6f972b31b3 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/ChannelParser.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.config; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.channel.PointToPointChannel; +import org.springframework.util.StringUtils; + +/** + * Parser for the channel element of the integration namespace. + * + * @author Mark Fisher + */ +public class ChannelParser implements BeanDefinitionParser { + + private static final String ID_ATTRIBUTE = "id"; + + private static final String CAPACITY_ATTRIBUTE = "capacity"; + + + public BeanDefinition parse(Element element, ParserContext parserContext) { + RootBeanDefinition channelDef = new RootBeanDefinition(PointToPointChannel.class); + channelDef.setSource(parserContext.extractSource(element)); + String capacity = element.getAttribute(CAPACITY_ATTRIBUTE); + if (StringUtils.hasText(capacity)) { + channelDef.getConstructorArgumentValues().addGenericArgumentValue(Integer.parseInt(capacity)); + } + String beanName = element.getAttribute(ID_ATTRIBUTE); + if (!StringUtils.hasText(beanName)) { + beanName = parserContext.getReaderContext().generateBeanName(channelDef); + } + parserContext.registerBeanComponent(new BeanComponentDefinition(channelDef, beanName)); + return channelDef; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/EndpointParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/EndpointParser.java new file mode 100644 index 0000000000..a04077145f --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/EndpointParser.java @@ -0,0 +1,173 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.config; + +import java.util.ArrayList; +import java.util.List; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.support.ManagedList; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.MessagingConfigurationException; +import org.springframework.integration.bus.ConsumerPolicy; +import org.springframework.integration.endpoint.GenericMessageEndpoint; +import org.springframework.integration.handler.DefaultMessageHandlerAdapter; +import org.springframework.integration.handler.MessageHandlerChain; +import org.springframework.util.StringUtils; + +/** + * Parser for the endpoint element of the integration namespace. + * + * @author Mark Fisher + */ +public class EndpointParser implements BeanDefinitionParser { + + private static final String ID_ATTRIBUTE = "id"; + + private static final String INPUT_CHANNEL_ATTRIBUTE = "input-channel"; + + private static final String INPUT_CHANNEL_PROPERTY = "inputChannelName"; + + private static final String DEFAULT_OUTPUT_CHANNEL_ATTRIBUTE = "default-output-channel"; + + private static final String DEFAULT_OUTPUT_CHANNEL_PROPERTY = "defaultOutputChannelName"; + + private static final String HANDLER_ELEMENT = "handler"; + + private static final String REF_ATTRIBUTE = "ref"; + + private static final String METHOD_ATTRIBUTE = "method"; + + private static final String HANDLERS_PROPERTY = "handlers"; + + private static final String HANDLER_REF_ATTRIBUTE = "handler-ref"; + + private static final String HANDLER_METHOD_ATTRIBUTE = "handler-method"; + + private static final String HANDLER_PROPERTY = "handler"; + + private static final String OBJECT_PROPERTY = "object"; + + private static final String METHOD_NAME_PROPERTY = "methodName"; + + private static final String PERIOD_ATTRIBUTE = "period"; + + private static final String PERIOD_PROPERTY = "period"; + + private static final String CONSUMER_ELEMENT = "consumer"; + + private static final String CONSUMER_POLICY_PROPERTY = "consumerPolicy"; + + + public BeanDefinition parse(Element element, ParserContext parserContext) { + RootBeanDefinition endpointDef = new RootBeanDefinition(GenericMessageEndpoint.class); + endpointDef.setSource(parserContext.extractSource(element)); + String inputChannel = element.getAttribute(INPUT_CHANNEL_ATTRIBUTE); + if (StringUtils.hasText(inputChannel)) { + endpointDef.getPropertyValues().addPropertyValue(INPUT_CHANNEL_PROPERTY, inputChannel); + } + String defaultOutputChannel = element.getAttribute(DEFAULT_OUTPUT_CHANNEL_ATTRIBUTE); + if (StringUtils.hasText(defaultOutputChannel)) { + endpointDef.getPropertyValues().addPropertyValue(DEFAULT_OUTPUT_CHANNEL_PROPERTY, defaultOutputChannel); + } + List childHandlerRefs = new ArrayList(); + NodeList childNodes = element.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node child = childNodes.item(i); + if (child.getNodeType() == Node.ELEMENT_NODE) { + String localName = child.getLocalName(); + if (CONSUMER_ELEMENT.equals(localName)) { + String consumerBeanName = parseConsumer((Element) child, parserContext); + endpointDef.getPropertyValues().addPropertyValue( + CONSUMER_POLICY_PROPERTY, new RuntimeBeanReference(consumerBeanName)); + } + else if (HANDLER_ELEMENT.equals(localName)) { + String ref = ((Element) child).getAttribute(REF_ATTRIBUTE); + String method = ((Element) child).getAttribute(METHOD_ATTRIBUTE); + childHandlerRefs.add(this.parseHandlerAdapter(ref, method, parserContext)); + } + } + } + if (childHandlerRefs.size() > 0) { + if (childHandlerRefs.size() == 1) { + endpointDef.getPropertyValues().addPropertyValue( + HANDLER_PROPERTY, new RuntimeBeanReference(childHandlerRefs.get(0))); + } + else { + RootBeanDefinition handlerChainDef = new RootBeanDefinition(MessageHandlerChain.class); + List handlerList = new ManagedList(); + for (String ref : childHandlerRefs) { + handlerList.add(new RuntimeBeanReference(ref)); + } + handlerChainDef.getPropertyValues().addPropertyValue(HANDLERS_PROPERTY, handlerList); + String chainBeanName = parserContext.getReaderContext().generateBeanName(handlerChainDef); + parserContext.registerBeanComponent(new BeanComponentDefinition(handlerChainDef, chainBeanName)); + endpointDef.getPropertyValues().addPropertyValue(HANDLER_PROPERTY, new RuntimeBeanReference(chainBeanName)); + } + } + String handlerRef = element.getAttribute(HANDLER_REF_ATTRIBUTE); + if (StringUtils.hasText(handlerRef)) { + if (childHandlerRefs.size() > 0) { + throw new MessagingConfigurationException( + "The 'handler-ref' attribute is only supported when no 'handler' child elements are present"); + } + String handlerMethod = element.getAttribute(HANDLER_METHOD_ATTRIBUTE); + if (StringUtils.hasText(handlerMethod)) { + String adapterBeanName = this.parseHandlerAdapter(handlerRef, handlerMethod, parserContext); + endpointDef.getPropertyValues().addPropertyValue(HANDLER_PROPERTY, new RuntimeBeanReference(adapterBeanName)); + } + else { + endpointDef.getPropertyValues().addPropertyValue(HANDLER_PROPERTY, new RuntimeBeanReference(handlerRef)); + } + } + String beanName = element.getAttribute(ID_ATTRIBUTE); + if (!StringUtils.hasText(beanName)) { + beanName = parserContext.getReaderContext().generateBeanName(endpointDef); + } + parserContext.registerBeanComponent(new BeanComponentDefinition(endpointDef, beanName)); + return endpointDef; + } + + private String parseConsumer(Element element, ParserContext parserContext) { + RootBeanDefinition consumerDef = new RootBeanDefinition(ConsumerPolicy.class); + String period = element.getAttribute(PERIOD_ATTRIBUTE); + if (StringUtils.hasText(period)) { + consumerDef.getPropertyValues().addPropertyValue(PERIOD_PROPERTY, Integer.parseInt(period)); + } + String beanName = parserContext.getReaderContext().generateBeanName(consumerDef); + parserContext.registerBeanComponent(new BeanComponentDefinition(consumerDef, beanName)); + return beanName; + } + + private String parseHandlerAdapter(String handlerRef, String handlerMethod, ParserContext parserContext) { + BeanDefinition handlerAdapterDef = new RootBeanDefinition(DefaultMessageHandlerAdapter.class); + handlerAdapterDef.getPropertyValues().addPropertyValue(OBJECT_PROPERTY, new RuntimeBeanReference(handlerRef)); + handlerAdapterDef.getPropertyValues().addPropertyValue(METHOD_NAME_PROPERTY, handlerMethod); + String adapterBeanName = parserContext.getReaderContext().generateBeanName(handlerAdapterDef); + parserContext.registerBeanComponent(new BeanComponentDefinition(handlerAdapterDef, adapterBeanName)); + return adapterBeanName; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationNamespaceHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationNamespaceHandler.java new file mode 100644 index 0000000000..4d17bb7bb6 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationNamespaceHandler.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.config; + +import org.springframework.beans.factory.xml.NamespaceHandlerSupport; +import org.springframework.integration.adapter.file.config.FileSourceAdapterParser; +import org.springframework.integration.adapter.file.config.FileTargetAdapterParser; +import org.springframework.integration.adapter.jms.config.JmsTargetAdapterParser; + +/** + * Namespace handler for the integration namespace. + * + * @author Mark Fisher + */ +public class IntegrationNamespaceHandler extends NamespaceHandlerSupport { + + public void init() { + registerBeanDefinitionParser("message-bus", new MessageBusParser()); + registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenParser()); + registerBeanDefinitionParser("channel", new ChannelParser()); + registerBeanDefinitionParser("source-adapter", new ChannelAdapterParser(true)); + registerBeanDefinitionParser("target-adapter", new ChannelAdapterParser(false)); + registerBeanDefinitionParser("endpoint", new EndpointParser()); + registerBeanDefinitionParser("file-source", new FileSourceAdapterParser()); + registerBeanDefinitionParser("file-target", new FileTargetAdapterParser()); + registerBeanDefinitionParser("jms-target", new JmsTargetAdapterParser()); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/MessageBusParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/MessageBusParser.java new file mode 100644 index 0000000000..dddad74466 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/MessageBusParser.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.config; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.bus.MessageBus; + +/** + * Parser for the message-bus element of the integration namespace. + * + * @author Mark Fisher + */ +public class MessageBusParser extends AbstractSimpleBeanDefinitionParser { + + public static final String MESSAGE_BUS_BEAN_NAME = "org.springframework.integration.bus.internalMessageBus"; + + @Override + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) + throws BeanDefinitionStoreException { + return MESSAGE_BUS_BEAN_NAME; + } + + @Override + protected Class getBeanClass(Element element) { + return MessageBus.class; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/MessageEndpointAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/MessageEndpointAnnotationPostProcessor.java new file mode 100644 index 0000000000..2f6f399bc0 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/MessageEndpointAnnotationPostProcessor.java @@ -0,0 +1,241 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.config; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.core.OrderComparator; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.integration.MessagingConfigurationException; +import org.springframework.integration.adapter.DefaultTargetAdapter; +import org.springframework.integration.adapter.MethodInvokingSource; +import org.springframework.integration.adapter.MethodInvokingTarget; +import org.springframework.integration.adapter.PollingSourceAdapter; +import org.springframework.integration.annotation.DefaultOutput; +import org.springframework.integration.annotation.Handler; +import org.springframework.integration.annotation.MessageEndpoint; +import org.springframework.integration.annotation.Polled; +import org.springframework.integration.annotation.Router; +import org.springframework.integration.annotation.Splitter; +import org.springframework.integration.bus.ConsumerPolicy; +import org.springframework.integration.bus.MessageBus; +import org.springframework.integration.channel.ChannelRegistryAware; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.channel.PointToPointChannel; +import org.springframework.integration.endpoint.GenericMessageEndpoint; +import org.springframework.integration.handler.MessageHandler; +import org.springframework.integration.handler.MessageHandlerChain; +import org.springframework.integration.handler.config.DefaultMessageHandlerCreator; +import org.springframework.integration.handler.config.MessageHandlerCreator; +import org.springframework.integration.handler.config.RouterMessageHandlerCreator; +import org.springframework.integration.handler.config.SplitterMessageHandlerCreator; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +/** + * A {@link BeanPostProcessor} implementation that generates endpoints for + * classes annotated with {@link MessageEndpoint @MessageEndpoint}. + * + * @author Mark Fisher + */ +public class MessageEndpointAnnotationPostProcessor implements BeanPostProcessor, InitializingBean { + + private Log logger = LogFactory.getLog(this.getClass()); + + private Map, MessageHandlerCreator> handlerCreators = + new ConcurrentHashMap, MessageHandlerCreator>(); + + private MessageBus messageBus; + + + public void setMessageBus(MessageBus messageBus) { + Assert.notNull(messageBus, "messageBus must not be null"); + this.messageBus = messageBus; + } + + public void setCustomHandlerCreators( + Map, MessageHandlerCreator> customHandlerCreators) { + for (Map.Entry, MessageHandlerCreator> entry : customHandlerCreators.entrySet()) { + this.handlerCreators.put(entry.getKey(), entry.getValue()); + } + } + + public void afterPropertiesSet() { + Assert.notNull(this.messageBus, "messageBus is required"); + this.handlerCreators.put(Handler.class, new DefaultMessageHandlerCreator()); + this.handlerCreators.put(Router.class, new RouterMessageHandlerCreator()); + this.handlerCreators.put(Splitter.class, new SplitterMessageHandlerCreator()); + } + + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + MessageEndpoint endpointAnnotation = bean.getClass().getAnnotation(MessageEndpoint.class); + if (endpointAnnotation == null) { + return bean; + } + GenericMessageEndpoint endpoint = new GenericMessageEndpoint(); + this.configureInputChannel(bean, beanName, endpointAnnotation, endpoint); + this.configureDefaultOutputChannel(bean, beanName, endpointAnnotation, endpoint); + MessageHandlerChain handlerChain = this.createHandlerChain(bean); + if (handlerChain != null) { + endpoint.setHandler(handlerChain); + } + this.messageBus.registerEndpoint(beanName, endpoint); + return endpoint; + } + + private void configureInputChannel(final Object bean, final String beanName, + MessageEndpoint annotation, final GenericMessageEndpoint endpoint) { + String channelName = annotation.input(); + if (StringUtils.hasText(channelName)) { + endpoint.setInputChannelName(channelName); + ConsumerPolicy consumerPolicy = new ConsumerPolicy(); + consumerPolicy.setPeriod(annotation.pollPeriod()); + endpoint.setConsumerPolicy(consumerPolicy); + return; + } + ReflectionUtils.doWithMethods(bean.getClass(), new ReflectionUtils.MethodCallback() { + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + Annotation annotation = AnnotationUtils.getAnnotation(method, Polled.class); + if (annotation != null) { + int period = ((Polled) annotation).period(); + MethodInvokingSource source = new MethodInvokingSource(); + source.setObject(bean); + source.setMethod(method.getName()); + PollingSourceAdapter adapter = new PollingSourceAdapter(source); + MessageChannel channel = new PointToPointChannel(); + adapter.setChannel(channel); + adapter.setPeriod(period); + String channelName = beanName + "-inputChannel"; + messageBus.registerChannel(channelName, channel); + messageBus.registerSourceAdapter(beanName + "-sourceAdapter", adapter); + endpoint.setInputChannelName(channelName); + endpoint.getConsumerPolicy().setPeriod(period); + if (period > 0) { + endpoint.getConsumerPolicy().setConcurrency(1); + endpoint.getConsumerPolicy().setMaxConcurrency(1); + endpoint.getConsumerPolicy().setMaxMessagesPerTask(1); + } + return; + } + } + }); + } + + private void configureDefaultOutputChannel(final Object bean, final String beanName, + final MessageEndpoint annotation, final GenericMessageEndpoint endpoint) { + String channelName = annotation.defaultOutput(); + if (StringUtils.hasText(channelName)) { + endpoint.setDefaultOutputChannelName(channelName); + return; + } + ReflectionUtils.doWithMethods(bean.getClass(), new ReflectionUtils.MethodCallback() { + boolean foundDefaultOutput = false; + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + Annotation annotation = AnnotationUtils.getAnnotation(method, DefaultOutput.class); + if (annotation != null) { + if (foundDefaultOutput) { + throw new MessagingConfigurationException("only one @DefaultOutput allowed per endpoint"); + } + MethodInvokingTarget target = new MethodInvokingTarget(); + target.setObject(bean); + target.setMethod(method.getName()); + target.afterPropertiesSet(); + DefaultTargetAdapter adapter = new DefaultTargetAdapter(target); + PointToPointChannel channel = new PointToPointChannel(); + String channelName = beanName + "-defaultOutputChannel"; + messageBus.registerChannel(channelName, channel); + messageBus.registerTargetAdapter(beanName + "-targetAdapter", adapter); + endpoint.setDefaultOutputChannelName(channelName); + foundDefaultOutput = true; + return; + } + } + }); + } + + @SuppressWarnings("unchecked") + private MessageHandlerChain createHandlerChain(final Object bean) { + final List handlers = new ArrayList(); + ReflectionUtils.doWithMethods(bean.getClass(), new ReflectionUtils.MethodCallback() { + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + Annotation[] annotations = AnnotationUtils.getAnnotations(method); + for (Annotation annotation : annotations) { + if (isHandlerAnnotation(annotation)) { + Map attributes = AnnotationUtils.getAnnotationAttributes(annotation); + MessageHandlerCreator handlerCreator = handlerCreators.get(annotation.annotationType()); + if (handlerCreator == null) { + if (logger.isWarnEnabled()) { + logger.warn("No handler creator has been registered for handler annotation '" + + annotation.annotationType() + "'"); + } + } + else { + MessageHandler handler = handlerCreator.createHandler(bean, method, attributes); + if (handler instanceof ChannelRegistryAware) { + ((ChannelRegistryAware) handler).setChannelRegistry(messageBus); + } + if (handler instanceof InitializingBean) { + try { + ((InitializingBean) handler).afterPropertiesSet(); + } + catch (Exception e) { + throw new MessagingConfigurationException("failed to create handler", e); + } + } + if (handler != null) { + handlers.add(handler); + } + } + } + } + } + }); + if (handlers.size() > 0) { + MessageHandlerChain handlerChain = new MessageHandlerChain(); + Collections.sort(handlers, new OrderComparator()); + for (MessageHandler handler : handlers) { + handlerChain.add(handler); + } + return handlerChain; + } + return null; + } + + private boolean isHandlerAnnotation(Annotation annotation) { + return annotation.annotationType().equals(Handler.class) + || annotation.annotationType().isAnnotationPresent(Handler.class) + || this.handlerCreators.keySet().contains(annotation.annotationType()); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/PublisherAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/PublisherAnnotationPostProcessor.java new file mode 100644 index 0000000000..c0347879e9 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/PublisherAnnotationPostProcessor.java @@ -0,0 +1,109 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.config; + +import java.lang.annotation.Annotation; + +import org.springframework.aop.Advisor; +import org.springframework.aop.framework.Advised; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.integration.annotation.Publisher; +import org.springframework.integration.aop.PublisherAnnotationAdvisor; +import org.springframework.integration.channel.ChannelRegistry; +import org.springframework.util.Assert; + +/** + * A {@link BeanPostProcessor} that adds a message publishing interceptor when + * it discovers annotated methods. + * + * @author Mark Fisher + */ +public class PublisherAnnotationPostProcessor implements BeanPostProcessor, BeanClassLoaderAware { + + private Class publisherAnnotationType = Publisher.class; + + private String channelNameAttribute = "channel"; + + private ChannelRegistry channelRegistry; + + private Advisor advisor; + + private ClassLoader beanClassLoader; + + + public void setBeanClassLoader(ClassLoader beanClassLoader) { + Assert.notNull(beanClassLoader, "'beanClassLoader' must not be null"); + this.beanClassLoader = beanClassLoader; + } + + public void setPublisherAnnotationType(Class publisherAnnotationType) { + Assert.notNull(publisherAnnotationType, "'publisherAnnotationType' must not be null"); + this.publisherAnnotationType = publisherAnnotationType; + } + + public void setChannelNameAttribute(String channelNameAttribute) { + Assert.notNull(channelNameAttribute, "'channelNameAttribute' must not be null"); + this.channelNameAttribute = channelNameAttribute; + } + + public void setChannelRegistry(ChannelRegistry channelRegistry) { + Assert.notNull(channelRegistry, "'channelRegistry' must not be null"); + this.channelRegistry = channelRegistry; + } + + private void createAdvisor() { + if (this.channelRegistry == null) { + throw new IllegalStateException("'channelRegistry' is required"); + } + this.advisor = new PublisherAnnotationAdvisor(this.publisherAnnotationType, this.channelNameAttribute, + this.channelRegistry); + } + + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + Class targetClass = bean instanceof Advised ? + ((Advised) bean).getTargetSource().getTargetClass() : bean.getClass(); + if (targetClass == null) { + return bean; + } + if (advisor == null) { + createAdvisor(); + } + if (AopUtils.canApply(this.advisor, targetClass)) { + if (bean instanceof Advised) { + ((Advised) bean).addAdvisor(this.advisor); + return bean; + } + else { + ProxyFactory pf = new ProxyFactory(bean); + pf.addAdvisor(this.advisor); + return pf.getProxy(this.beanClassLoader); + } + } + else { + return bean; + } + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/SubscriberAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/SubscriberAnnotationPostProcessor.java new file mode 100644 index 0000000000..a0a5aaa64e --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/SubscriberAnnotationPostProcessor.java @@ -0,0 +1,101 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.config; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.aop.framework.Advised; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.integration.annotation.Subscriber; +import org.springframework.integration.bus.MessageBus; +import org.springframework.integration.endpoint.GenericMessageEndpoint; +import org.springframework.integration.handler.DefaultMessageHandlerAdapter; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +/** + * A {@link BeanPostProcessor} that creates a method-invoking handler adapter + * when it discovers methods annotated with {@link Subscriber @Subscriber}. + * + * @author Mark Fisher + */ +public class SubscriberAnnotationPostProcessor implements BeanPostProcessor { + + private Log logger = LogFactory.getLog(this.getClass()); + + private Class subscriberAnnotationType = Subscriber.class; + + private String channelNameAttribute = "channel"; + + private MessageBus messageBus; + + + public void setSubscriberAnnotationType(Class subscriberAnnotationType) { + Assert.notNull(subscriberAnnotationType, "'subscriberAnnotationType' must not be null"); + this.subscriberAnnotationType = subscriberAnnotationType; + } + + public void setChannelNameAttribute(String channelNameAttribute) { + Assert.notNull(channelNameAttribute, "'channelNameAttribute' must not be null"); + this.channelNameAttribute = channelNameAttribute; + } + + public void setMessageBus(MessageBus messageBus) { + Assert.notNull(messageBus, "'messageBus' must not be null"); + this.messageBus = messageBus; + } + + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException { + final Class targetClass = bean instanceof Advised ? + ((Advised) bean).getTargetSource().getTargetClass() : bean.getClass(); + if (targetClass == null) { + return bean; + } + ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() { + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + Annotation annotation = method.getAnnotation(subscriberAnnotationType); + if (annotation != null) { + String channelName = (String) AnnotationUtils.getValue(annotation, channelNameAttribute); + DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); + adapter.setMethodName(method.getName()); + adapter.setObject(bean); + adapter.afterPropertiesSet(); + GenericMessageEndpoint endpoint = new GenericMessageEndpoint(); + endpoint.setInputChannelName(channelName); + endpoint.setChannelRegistry(messageBus); + endpoint.setHandler(adapter); + String endpointName = ClassUtils.getShortNameAsProperty(targetClass) + + "-" + method.getName() + "-endpoint"; + messageBus.registerEndpoint(endpointName, endpoint); + } + } + }); + return bean; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/spring-integration-1.0.xsd b/spring-integration-core/src/main/java/org/springframework/integration/config/spring-integration-1.0.xsd new file mode 100644 index 0000000000..2d527797f8 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/spring-integration-1.0.xsd @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + Defines a message bus. + + + + + + + + + + + + Enables the annotation-driven post-processors. + + + + + + + + + + Defines a message channel. + + + + + + + + + + + + + + + Defines a source (inbound) channel adapter. + + + + + + + + + + + + + + + + + + Defines a target (outbound) channel adapter. + + + + + + + + + + + + + + + + + Defines a message endpoint. + + + + + + + + + + + + + + + + + + + + + + Defines a consumer policy. + + + + + + + + + + + Defines a handler. + + + + + + + + + + + + Defines a file-based source channel adapter. + + + + + + + + + + + + + Defines a file-based target channel adapter. + + + + + + + + + + + + Defines a jms-based target channel adapter. + + + + + + + + + + \ No newline at end of file diff --git a/spring-integration-core/src/main/java/org/springframework/integration/endpoint/GenericMessageEndpoint.java b/spring-integration-core/src/main/java/org/springframework/integration/endpoint/GenericMessageEndpoint.java new file mode 100644 index 0000000000..950ba69f59 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/endpoint/GenericMessageEndpoint.java @@ -0,0 +1,137 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.endpoint; + +import org.springframework.integration.MessageHandlingException; +import org.springframework.integration.MessagingConfigurationException; +import org.springframework.integration.bus.ConsumerPolicy; +import org.springframework.integration.channel.ChannelRegistry; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.handler.MessageHandler; +import org.springframework.integration.message.Message; + +/** + * A generic endpoint implementation designed to accommodate a variety of + * strategies including: + *
    + *
  • source channel-adapter: source adapter + target channel
  • + *
  • target channel-adapter: source channel + target adapter
  • + *
  • one-way: source + handler that returns null and no target
  • + *
  • request-reply: source + handler and either a reply channel + * specified on the request message or a default target on the endpoint + *
+ * + * @author Mark Fisher + */ +public class GenericMessageEndpoint implements MessageEndpoint { + + private String inputChannelName; + + private String defaultOutputChannelName; + + private MessageHandler handler; + + private ChannelRegistry channelRegistry; + + private ConsumerPolicy consumerPolicy = new ConsumerPolicy(); + + + /** + * Set the name of the channel from which this endpoint receives messages. + */ + public void setInputChannelName(String inputChannelName) { + this.inputChannelName = inputChannelName; + } + + /** + * Return the name of the channel from which this endpoint receives messages. + */ + public String getInputChannelName() { + return this.inputChannelName; + } + + public void setConsumerPolicy(ConsumerPolicy consumerPolicy) { + this.consumerPolicy = consumerPolicy; + } + + public ConsumerPolicy getConsumerPolicy() { + return this.consumerPolicy; + } + + /** + * Set the name of the channel to which this endpoint can send reply messages by default. + */ + public void setDefaultOutputChannelName(String defaultOutputChannelName) { + this.defaultOutputChannelName = defaultOutputChannelName; + } + + public String getDefaultOutputChannelName() { + return this.defaultOutputChannelName; + } + + /** + * Set a handler to be invoked for each consumed message. + */ + public void setHandler(MessageHandler handler) { + this.handler = handler; + } + + /** + * Set the channel registry to use for looking up channels by name. + */ + public void setChannelRegistry(ChannelRegistry channelRegistry) { + this.channelRegistry = channelRegistry; + } + + + public void messageReceived(Message message) { + if (this.handler == null) { + if (this.defaultOutputChannelName == null) { + throw new MessagingConfigurationException( + "endpoint must have either a 'handler' or 'defaultOutputChannelName'"); + } + MessageChannel replyChannel = this.channelRegistry.lookupChannel(this.defaultOutputChannelName); + replyChannel.send(message); + return; + } + Message replyMessage = handler.handle(message); + if (replyMessage != null) { + MessageChannel replyChannel = this.resolveReplyChannel(message); + if (replyChannel == null) { + throw new MessageHandlingException("Unable to determine reply channel for message. " + + "Provide a 'replyChannelName' in the message header or a 'defaultOutputChannelName' " + + "on the message endpoint."); + } + replyChannel.send(replyMessage); + } + } + + private MessageChannel resolveReplyChannel(Message message) { + if (this.channelRegistry == null) { + return null; + } + String replyChannelName = message.getHeader().getReplyChannelName(); + if (replyChannelName != null && replyChannelName.trim().length() > 0) { + return this.channelRegistry.lookupChannel(replyChannelName); + } + if (this.defaultOutputChannelName != null) { + return this.channelRegistry.lookupChannel(this.defaultOutputChannelName); + } + return null; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/endpoint/MessageEndpoint.java b/spring-integration-core/src/main/java/org/springframework/integration/endpoint/MessageEndpoint.java new file mode 100644 index 0000000000..a9d0dc161d --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/endpoint/MessageEndpoint.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.endpoint; + +import org.springframework.integration.bus.ConsumerPolicy; +import org.springframework.integration.channel.ChannelRegistry; +import org.springframework.integration.message.MessageReceiver; + +/** + * Base interface for message endpoints. + * + * @author Mark Fisher + */ +public interface MessageEndpoint extends MessageReceiver { + + void setInputChannelName(String inputChannelName); + + String getInputChannelName(); + + void setDefaultOutputChannelName(String defaultOutputChannelName); + + String getDefaultOutputChannelName(); + + ConsumerPolicy getConsumerPolicy(); + + void setChannelRegistry(ChannelRegistry channelRegistry); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractMessageHandlerAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractMessageHandlerAdapter.java new file mode 100644 index 0000000000..9761f80e28 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractMessageHandlerAdapter.java @@ -0,0 +1,132 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.handler; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.Ordered; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessageMapper; +import org.springframework.integration.message.SimplePayloadMessageMapper; +import org.springframework.integration.util.SimpleMethodInvoker; +import org.springframework.util.Assert; + +/** + * Base implementation of the {@link MessageHandler} interface that creates an + * invoker for the specified method and target object. It also accepts an + * implementation of the {@link MessageMapper} strategy which it exposes to + * subclasses for converting the {@link Message} to an object. Likewise, if the + * method has a non-null return value, a reply message will be generated by the + * mapper. + * + * @author Mark Fisher + */ +public abstract class AbstractMessageHandlerAdapter implements MessageHandler, Ordered, InitializingBean { + + protected Log logger = LogFactory.getLog(this.getClass()); + + private T object; + + private String methodName; + + private MessageMapper mapper = new SimplePayloadMessageMapper(); + + private SimpleMethodInvoker invoker; + + private int order = Integer.MAX_VALUE; + + private volatile boolean initialized; + + private Object lifecycleMonitor = new Object(); + + + public void setObject(T object) { + Assert.notNull(object, "'object' must not be null"); + this.object = object; + } + + protected Object getObject() { + return this.object; + } + + public void setMethodName(String methodName) { + Assert.notNull(methodName, "'methodName' must not be null"); + this.methodName = methodName; + } + + public String getMethodName() { + return this.methodName; + } + + public void setMapper(MessageMapper mapper) { + Assert.notNull(mapper, "'mapper' must not be null"); + this.mapper = mapper; + } + + protected MessageMapper getMapper() { + return this.mapper; + } + + public void setOrder(int order) { + this.order = order; + } + + public int getOrder() { + return this.order; + } + + public final void afterPropertiesSet() { + this.validate(); + synchronized (this.lifecycleMonitor) { + this.invoker = new SimpleMethodInvoker(this.object, this.methodName); + this.initialized = true; + } + } + + public final boolean isInitialized() { + synchronized (this.lifecycleMonitor) { + return this.initialized; + } + } + + public final Message handle(Message message) { + Object result = this.doHandle(message, invoker); + if (result != null) { + if (result instanceof Message) { + return (Message) result; + } + return this.mapper.toMessage(result); + } + return null; + } + + /** + * Subclasses may override this method to provide validation upon initialization. + */ + protected void validate() { + } + + /** + * Subclasses must implement this method. The invoker has been created for + * the provided target object and method. May return an object of type + * {@link Message}, else rely on the message mapper to convert. + */ + protected abstract Object doHandle(Message message, SimpleMethodInvoker invoker); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/DefaultMessageHandlerAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/DefaultMessageHandlerAdapter.java new file mode 100644 index 0000000000..e7eae7c9e0 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/DefaultMessageHandlerAdapter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.handler; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.Ordered; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessageMapper; +import org.springframework.integration.util.SimpleMethodInvoker; + +/** + * An implementation of {@link MessageHandler} that invokes the specified method + * on the provided target object. It then uses a {@link MessageMapper} strategy + * for converting the object to a {@link Message}. If the method has a non-null + * return value, a reply message will be generated by the mapper. + * + * @author Mark Fisher + */ +public class DefaultMessageHandlerAdapter extends AbstractMessageHandlerAdapter + implements Ordered, InitializingBean { + + public Object doHandle(Message message, SimpleMethodInvoker invoker) { + return invoker.invokeMethod(this.getMapper().fromMessage(message)); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/InterceptingMessageHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/InterceptingMessageHandler.java new file mode 100644 index 0000000000..bf1cd69637 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/InterceptingMessageHandler.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.handler; + +import org.springframework.integration.message.Message; +import org.springframework.util.Assert; + +/** + * A message handler implementation that intercepts calls to another handler. + * + * @author Mark Fisher + */ +public abstract class InterceptingMessageHandler implements MessageHandler { + + private MessageHandler target; + + + public InterceptingMessageHandler(MessageHandler target) { + Assert.notNull(target, "target must not be null"); + this.target = target; + } + + public Message handle(Message message) { + return handle(message, this.target); + } + + + /** + * The handler method for subclasses to implement. + * + * @param message the message to handle + * @param target the intercepted handler + * @return a reply message or null + */ + public abstract Message handle(Message message, MessageHandler target); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/MessageHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/MessageHandler.java new file mode 100644 index 0000000000..13a87f02b7 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/MessageHandler.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.handler; + +import org.springframework.integration.message.Message; + +/** + * Generic message handler interface. Typical implementations will translate + * between the generic Messages of the integration framework and the domain + * objects that are passed-to and returned-from business components. + * + * @author Mark Fisher + */ +public interface MessageHandler { + + Message handle(Message message); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/MessageHandlerChain.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/MessageHandlerChain.java new file mode 100644 index 0000000000..569354d995 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/MessageHandlerChain.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.handler; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.springframework.integration.message.Message; + +/** + * A message handler implementation that passes incoming messages through a + * chain of handlers. + * + * @author Mark Fisher + */ +public class MessageHandlerChain implements MessageHandler { + + private List handlers = new CopyOnWriteArrayList(); + + + /** + * Add a handler to the end of the chain. + */ + public void add(MessageHandler handler) { + this.handlers.add(handler); + } + + /** + * Add a handler to the chain at the specified index. + */ + public void add(int index, MessageHandler handler) { + this.handlers.add(index, handler); + } + + public void setHandlers(List handlers) { + this.handlers.clear(); + this.handlers.addAll(handlers); + } + + public Message handle(Message message) { + for (MessageHandler next : handlers) { + message = next.handle(message); + if (message == null) { + return null; + } + } + return message; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/config/AbstractMessageHandlerCreator.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/config/AbstractMessageHandlerCreator.java new file mode 100644 index 0000000000..56aa3c3ce3 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/config/AbstractMessageHandlerCreator.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.handler.config; + +import java.lang.reflect.Method; +import java.util.Map; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.Order; +import org.springframework.integration.handler.AbstractMessageHandlerAdapter; +import org.springframework.integration.handler.MessageHandler; + +/** + * Base class for handler creators that generate a {@link MessageHandler} + * adapter for the provided object and method. + * + * @author Mark Fisher + */ +public abstract class AbstractMessageHandlerCreator implements MessageHandlerCreator { + + public final MessageHandler createHandler(Object object, Method method, Map attributes) { + MessageHandler handler = this.doCreateHandler(object, method, attributes); + if (handler instanceof AbstractMessageHandlerAdapter) { + AbstractMessageHandlerAdapter adapter = ((AbstractMessageHandlerAdapter) handler); + adapter.setObject(object); + adapter.setMethodName(method.getName()); + Order orderAnnotation = (Order) AnnotationUtils.getAnnotation(method, Order.class); + if (orderAnnotation != null) { + adapter.setOrder(orderAnnotation.value()); + } + } + return handler; + } + + protected abstract MessageHandler doCreateHandler(Object object, Method method, Map attributes); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/config/DefaultMessageHandlerCreator.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/config/DefaultMessageHandlerCreator.java new file mode 100644 index 0000000000..f767594b25 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/config/DefaultMessageHandlerCreator.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.handler.config; + +import java.lang.reflect.Method; +import java.util.Map; + +import org.springframework.core.annotation.Order; +import org.springframework.integration.handler.DefaultMessageHandlerAdapter; +import org.springframework.integration.handler.MessageHandler; + +/** + * Default implementation of the handler creator strategy that creates a + * {@link DefaultMessageHandlerAdapter} for the provided object and method. + * This version does not even consider the attributes. It does however respect + * an {@link Order} annotation if present. + * + * @author Mark Fisher + */ +public class DefaultMessageHandlerCreator extends AbstractMessageHandlerCreator { + + public MessageHandler doCreateHandler(Object object, Method method, Map attributes) { + return new DefaultMessageHandlerAdapter(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/config/MessageHandlerCreator.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/config/MessageHandlerCreator.java new file mode 100644 index 0000000000..f03b1e3604 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/config/MessageHandlerCreator.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.handler.config; + +import java.lang.reflect.Method; +import java.util.Map; + +import org.springframework.integration.handler.MessageHandler; + +/** + * Strategy interface for creating {@link MessageHandler MessageHandlers}. + * + * @author Mark Fisher + */ +public interface MessageHandlerCreator { + + MessageHandler createHandler(Object object, Method method, Map attributes); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/config/RouterMessageHandlerCreator.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/config/RouterMessageHandlerCreator.java new file mode 100644 index 0000000000..1813a08c70 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/config/RouterMessageHandlerCreator.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.handler.config; + +import java.lang.reflect.Method; +import java.util.Map; + +import org.springframework.integration.handler.MessageHandler; +import org.springframework.integration.router.RouterMessageHandlerAdapter; + +/** + * Creates a {@link MessageHandler} adapter for router methods. + * + * @author Mark Fisher + */ +public class RouterMessageHandlerCreator extends AbstractMessageHandlerCreator { + + public MessageHandler doCreateHandler(Object object, Method method, Map attributes) { + return new RouterMessageHandlerAdapter(object, method, attributes); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/config/SplitterMessageHandlerCreator.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/config/SplitterMessageHandlerCreator.java new file mode 100644 index 0000000000..e5c34f873a --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/config/SplitterMessageHandlerCreator.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.handler.config; + +import java.lang.reflect.Method; +import java.util.Map; + +import org.springframework.integration.handler.MessageHandler; +import org.springframework.integration.router.SplitterMessageHandlerAdapter; + +/** + * Creates a {@link MessageHandler} adapter for splitter methods. + * + * @author Mark Fisher + */ +public class SplitterMessageHandlerCreator extends AbstractMessageHandlerCreator { + + public MessageHandler doCreateHandler(Object object, Method method, Map attributes) { + return new SplitterMessageHandlerAdapter(object, method, attributes); + } +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/message/AbstractMessageMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/message/AbstractMessageMapper.java new file mode 100644 index 0000000000..db48193a91 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/message/AbstractMessageMapper.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.message; + +import org.springframework.integration.util.RandomGuidUidGenerator; +import org.springframework.integration.util.UidGenerator; +import org.springframework.util.Assert; + +/** + * Base class that provides the default {@link UidGenerator} as well as a setter + * for providing a custom id generator implementation. + * + * @author Mark Fisher + */ +public abstract class AbstractMessageMapper implements MessageMapper { + + private UidGenerator uidGenerator = new RandomGuidUidGenerator(); + + + public void setUidGenerator(UidGenerator uidGenerator) { + Assert.notNull(uidGenerator, "'uidGenerator' must not be null"); + this.uidGenerator = uidGenerator; + } + + public UidGenerator getUidGenerator() { + return this.uidGenerator; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/message/GenericMessage.java b/spring-integration-core/src/main/java/org/springframework/integration/message/GenericMessage.java new file mode 100644 index 0000000000..65cebaeede --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/message/GenericMessage.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.message; + +import java.util.concurrent.locks.ReentrantLock; + +import org.springframework.integration.SystemInterruptedException; +import org.springframework.util.Assert; + +/** + * Base Message class defining common properties such as id, header, and lock. + * + * @author Mark Fisher + */ +public class GenericMessage implements Message { + + private Object id; + + private MessageHeader header = new MessageHeader(); + + private T payload; + + private ReentrantLock lock; + + + public GenericMessage(Object id, T payload) { + Assert.notNull(id, "id must not be null"); + Assert.notNull(payload, "payload must not be null"); + this.id = id; + this.payload = payload; + } + + public Object getId() { + return this.id; + } + + public MessageHeader getHeader() { + return this.header; + } + + protected void setHeader(MessageHeader header) { + this.header = header; + } + + public T getPayload() { + return this.payload; + } + + protected void setPayload(T newPayload) { + this.payload = newPayload; + } + + public void lock() { + this.lock.lock(); + } + + public void lockInterruptibly() { + try { + lock.lockInterruptibly(); + } + catch (InterruptedException e) { + throw new SystemInterruptedException("Unable to obtain lock for message", e); + } + } + + public void unlock() { + lock.unlock(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/message/Message.java b/spring-integration-core/src/main/java/org/springframework/integration/message/Message.java new file mode 100644 index 0000000000..bff4930903 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/message/Message.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.message; + +/** + * The central interface that any Message type must implement. + * + * @author Mark Fisher + */ +public interface Message { + + Object getId(); + + MessageHeader getHeader(); + + T getPayload(); + + void lock(); + + void unlock(); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/message/MessageHeader.java b/spring-integration-core/src/main/java/org/springframework/integration/message/MessageHeader.java new file mode 100644 index 0000000000..bee2950938 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/message/MessageHeader.java @@ -0,0 +1,114 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.message; + +import java.util.Date; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A holder for Message metadata. This includes information that is used by the + * messaging system (such correlationId) as well as information that is + * relevant for specific messaging endpoints. For the latter, String values may + * be stored as properties and Object values may be stored as + * attributes. + * + * @author Mark Fisher + */ +public class MessageHeader { + + private Object correlationId; + + private String replyChannelName; + + private Date expiration; + + private int sequenceNumber = 1; + + private int sequenceSize = 1; + + private Properties properties = new Properties(); + + private Map attributes = new ConcurrentHashMap(); + + + public Object getCorrelationId() { + return this.correlationId; + } + + public void setCorrelationId(Object correlationId) { + this.correlationId = correlationId; + } + + public String getReplyChannelName() { + return this.replyChannelName; + } + + public void setReplyChannelName(String replyChannelName) { + this.replyChannelName = replyChannelName; + } + + public int getSequenceNumber() { + return this.sequenceNumber; + } + + /** + * Set the expiration date for this message or null to + * indicate 'never expire'. The default is null. + */ + public void setExpiration(Date expiration) { + this.expiration = expiration; + } + + /** + * Return the expiration date for this message or null to + * indicate 'never expire'. + */ + public Date getExpiration() { + return this.expiration; + } + + public void setSequenceNumber(int sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + public int getSequenceSize() { + return this.sequenceSize; + } + + public void setSequenceSize(int sequenceSize) { + this.sequenceSize = sequenceSize; + } + + public String getProperty(String key) { + return this.properties.getProperty(key); + } + + public void setProperty(String key, String value) { + this.properties.setProperty(key, value); + } + + public Object getAttribute(String key) { + return this.attributes.get(key); + } + + public void setAttribute(String key, Object value) { + this.attributes.put(key, value); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/message/MessageMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/message/MessageMapper.java new file mode 100644 index 0000000000..e673868701 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/message/MessageMapper.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.message; + +/** + * Strategy interface for mapping between messages and objects. + * + * @author Mark Fisher + */ +public interface MessageMapper { + + /** + * Map to a {@link Message} from the given object. + */ + Message toMessage(O source); + + /** + * Map from the given {@link Message} to an object. + */ + O fromMessage(Message message); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/message/MessageReceiver.java b/spring-integration-core/src/main/java/org/springframework/integration/message/MessageReceiver.java new file mode 100644 index 0000000000..931e9ed253 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/message/MessageReceiver.java @@ -0,0 +1,29 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.message; + +/** + * The primary callback interface for any component capable of receiving + * messages. This includes message endpoints as well as target adapters. + * + * @author Mark Fisher + */ +public interface MessageReceiver { + + void messageReceived(Message message); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/message/MessageSelector.java b/spring-integration-core/src/main/java/org/springframework/integration/message/MessageSelector.java new file mode 100644 index 0000000000..a6278dc223 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/message/MessageSelector.java @@ -0,0 +1,28 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.message; + +/** + * Strategy interface for message selection. + * + * @author Mark Fisher + */ +public interface MessageSelector { + + boolean accept(Message message); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/message/SimplePayloadMessageMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/message/SimplePayloadMessageMapper.java new file mode 100644 index 0000000000..cab790d70d --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/message/SimplePayloadMessageMapper.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.message; + +/** + * A {@link MessageMapper} implementation that simply wraps and unwraps a + * payload object in a {@link DocumentMessage}. + * + * @author Mark Fisher + */ +public class SimplePayloadMessageMapper extends AbstractMessageMapper { + + /** + * Return the payload of the given Message. + */ + public T fromMessage(Message message) { + return message.getPayload(); + } + + /** + * Return a {@link DocumentMessage} with the given object as its payload. + */ + public Message toMessage(T source) { + return new GenericMessage(this.getUidGenerator().generateUid(), source); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/message/StringMessage.java b/spring-integration-core/src/main/java/org/springframework/integration/message/StringMessage.java new file mode 100644 index 0000000000..e9c7fd4342 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/message/StringMessage.java @@ -0,0 +1,28 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.message; + +/** + * @author Mark Fisher + */ +public class StringMessage extends GenericMessage { + + public StringMessage(Object id, String payload) { + super(id, payload); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractRoutingMessageHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractRoutingMessageHandler.java new file mode 100644 index 0000000000..c9cd9e93a6 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractRoutingMessageHandler.java @@ -0,0 +1,113 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.router; + +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.integration.MessageHandlingException; +import org.springframework.integration.MessagingConfigurationException; +import org.springframework.integration.channel.ChannelRegistry; +import org.springframework.integration.channel.ChannelRegistryAware; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.handler.MessageHandler; +import org.springframework.integration.message.Message; + +/** + * Base class for message router implementations. + * + * @author Mark Fisher + */ +public abstract class AbstractRoutingMessageHandler implements MessageHandler, ChannelRegistryAware, InitializingBean { + + protected Log logger = LogFactory.getLog(this.getClass()); + + private ChannelRegistry channelRegistry; + + private boolean resolutionRequired = false; + + private long timeout = -1; + + + /** + * Set whether this router should always be required to resolve at least one + * channel. The default is 'false'. To trigger an exception whenever the + * resolver returns null or an empty channel list, set this value to 'true'. + */ + public void setResolutionRequired(boolean resolutionRequired) { + this.resolutionRequired = resolutionRequired; + } + + /** + * Set the timeout for sending a message to the resolved channel. By + * default, there is no timeout, meaning the send will block indefinitely. + */ + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + public void setChannelRegistry(ChannelRegistry channelRegistry) { + this.channelRegistry = channelRegistry; + } + + protected ChannelRegistry getChannelRegistry() { + return this.channelRegistry; + } + + public final void afterPropertiesSet() { + this.validate(); + } + + public final Message handle(Message message) { + List channels = this.resolveChannels(message); + if (channels == null || channels.size() == 0) { + String errorMessage = "failed to resolve any channel for message"; + if (this.resolutionRequired) { + throw new MessageHandlingException(errorMessage); + } + if (logger.isWarnEnabled()) { + logger.warn(errorMessage); + } + return null; + } + for (MessageChannel channel : channels) { + this.sendMesage(message, channel); + } + return null; + } + + private void sendMesage(Message message, MessageChannel channel) { + boolean sent = false; + if (timeout < 0) { + sent = channel.send(message); + } + else { + sent = channel.send(message, timeout); + } + if (!sent) { + throw new MessageHandlingException("failed to send message to channel '" + channel.getName() + "'"); + } + } + + protected abstract void validate() throws MessagingConfigurationException; + + protected abstract List resolveChannels(Message message); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/Aggregator.java b/spring-integration-core/src/main/java/org/springframework/integration/router/Aggregator.java new file mode 100644 index 0000000000..ab4e882e8a --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/Aggregator.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.router; + +import java.util.Collection; + +/** + * Base interface for aggregators. + * + * @author Mark Fisher + */ +public interface Aggregator { + + T aggregate(Collection t); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/ChannelNameResolver.java b/spring-integration-core/src/main/java/org/springframework/integration/router/ChannelNameResolver.java new file mode 100644 index 0000000000..09a9f00505 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/ChannelNameResolver.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.router; + +import org.springframework.integration.message.Message; + +/** + * Strategy interface for content-based routing to a channel name. + * + * @author Mark Fisher + */ +public interface ChannelNameResolver { + + String resolve(Message message); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/ChannelResolver.java b/spring-integration-core/src/main/java/org/springframework/integration/router/ChannelResolver.java new file mode 100644 index 0000000000..89881c2809 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/ChannelResolver.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.router; + +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.message.Message; + +/** + * Strategy interface for content-based routing to a channel instance. + * + * @author Mark Fisher + */ +public interface ChannelResolver { + + MessageChannel resolve(Message message); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/MultiChannelNameResolver.java b/spring-integration-core/src/main/java/org/springframework/integration/router/MultiChannelNameResolver.java new file mode 100644 index 0000000000..12e25211e6 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/MultiChannelNameResolver.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.router; + +import org.springframework.integration.message.Message; + +/** + * Strategy interface for content-based routing to multiple channel names. + * + * @author Mark Fisher + */ +public interface MultiChannelNameResolver { + + String[] resolve(Message message); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/MultiChannelResolver.java b/spring-integration-core/src/main/java/org/springframework/integration/router/MultiChannelResolver.java new file mode 100644 index 0000000000..4a41969641 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/MultiChannelResolver.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.router; + +import java.util.List; + +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.message.Message; + +/** + * Strategy interface for content-based routing to multiple channels. + * + * @author Mark Fisher + */ +public interface MultiChannelResolver { + + List resolve(Message message); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/MultiChannelRouter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/MultiChannelRouter.java new file mode 100644 index 0000000000..0f89da60de --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/MultiChannelRouter.java @@ -0,0 +1,86 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.router; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.integration.MessagingConfigurationException; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.message.Message; + +/** + * A router implementation for sending to potentially multiple + * {@link MessageChannel MessageChannels}. Requires either a + * {@link MultiChannelResolver} or {@link MultiChannelNameResolver} strategy + * instance. In the case of the latter, the + * {@link org.springframework.integration.channel.ChannelRegistry} reference + * must also be provided. For convenience, the superclass does implement + * {@link org.springframework.integration.channel.ChannelRegistryAware}. + * + * @author Mark Fisher + */ +public class MultiChannelRouter extends AbstractRoutingMessageHandler { + + private MultiChannelResolver channelResolver; + + private MultiChannelNameResolver channelNameResolver; + + + public void setChannelResolver(MultiChannelResolver channelResolver) { + this.channelResolver = channelResolver; + } + + public void setChannelNameResolver(MultiChannelNameResolver channelNameResolver) { + this.channelNameResolver = channelNameResolver; + } + + @Override + public void validate() { + if (!(this.channelResolver != null ^ this.channelNameResolver != null)) { + throw new MessagingConfigurationException( + "exactly one of 'channelResolver' or 'channelNameResolver' must be provided"); + } + if (this.channelNameResolver != null && this.getChannelRegistry() == null) { + throw new MessagingConfigurationException("'channelRegistry' is required when resolving by channel name"); + } + } + + @Override + public List resolveChannels(Message message) { + if (this.channelResolver != null) { + return this.channelResolver.resolve(message); + } + if (this.channelNameResolver == null || this.getChannelRegistry() == null) { + throw new MessagingConfigurationException("router configuration requires either " + + "a 'channelResolver' or both 'channelNameResolver' and 'channelRegistry'"); + } + String[] channelNames = this.channelNameResolver.resolve(message); + if (channelNames == null) { + return null; + } + List channels = new ArrayList(channelNames.length); + for (String channelName : channelNames) { + MessageChannel channel = this.getChannelRegistry().lookupChannel(channelName); + if (channel != null) { + channels.add(channel); + } + } + return channels; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/RecipientListRouter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/RecipientListRouter.java new file mode 100644 index 0000000000..5f5753288a --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/RecipientListRouter.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.router; + +import java.util.List; + +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.message.Message; + +/** + * A simple extension of {@link MultiChannelRouter} that routes to a statically + * configured list of recipients. The recipients are provided either as a list + * of {@link MessageChannel} instances or as a String array of channel names. + * For dynamic recipient lists, implement either {@link MultiChannelResolver} or + * {@link MultiChannelNameResolver} and then explicitly configure an instance of + * {@link MultiChannelRouter}. + * + * @author Mark Fisher + */ +public class RecipientListRouter extends MultiChannelRouter { + + public void setChannelNames(String[] channelNames) { + this.setChannelNameResolver(new RecipientListChannelNameResolver(channelNames)); + } + + public void setChannels(List channels) { + this.setChannelResolver(new RecipientListChannelResolver(channels)); + } + + + private static class RecipientListChannelResolver implements MultiChannelResolver { + + private List channels; + + RecipientListChannelResolver(List channels) { + this.channels = channels; + } + + public List resolve(Message message) { + return this.channels; + } + } + + + private static class RecipientListChannelNameResolver implements MultiChannelNameResolver { + + private String[] channelNames; + + RecipientListChannelNameResolver(String[] channelNames) { + this.channelNames = channelNames; + } + + public String[] resolve(Message message) { + return this.channelNames; + } + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/Resequencer.java b/spring-integration-core/src/main/java/org/springframework/integration/router/Resequencer.java new file mode 100644 index 0000000000..72dba380c4 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/Resequencer.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.router; + +import java.util.Collection; +import java.util.List; + +/** + * Base interface for resequencers. + * + * @author Mark Fisher + */ +public interface Resequencer { + + List resequence(Collection t); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/RouterMessageHandlerAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/RouterMessageHandlerAdapter.java new file mode 100644 index 0000000000..f545393fb7 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/RouterMessageHandlerAdapter.java @@ -0,0 +1,167 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.router; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Map; + +import org.springframework.integration.MessagingConfigurationException; +import org.springframework.integration.annotation.Router; +import org.springframework.integration.channel.ChannelRegistry; +import org.springframework.integration.channel.ChannelRegistryAware; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.handler.AbstractMessageHandlerAdapter; +import org.springframework.integration.message.Message; +import org.springframework.integration.util.SimpleMethodInvoker; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * MessageHandler adapter for methods annotated with {@link Router @Router}. + * + * @author Mark Fisher + */ +public class RouterMessageHandlerAdapter extends AbstractMessageHandlerAdapter implements ChannelRegistryAware { + + private static final String PROPERTY_KEY = "property"; + + private static final String ATTRIBUTE_KEY = "attribute"; + + + private Method method; + + private Map attributes; + + private ChannelRegistry channelRegistry; + + + public RouterMessageHandlerAdapter(Object object, Method method, Map attributes) { + Assert.notNull(object, "'object' must not be null"); + Assert.notNull(method, "'method' must not be null"); + Assert.notNull(attributes, "'attributes' must not be null"); + this.setObject(object); + this.setMethodName(method.getName()); + this.method = method; + this.attributes = attributes; + } + + public void setChannelRegistry(ChannelRegistry channelRegistry) { + this.channelRegistry = channelRegistry; + } + + @Override + protected Object doHandle(Message message, SimpleMethodInvoker invoker) { + if (!this.isInitialized()) { + this.afterPropertiesSet(); + } + if (method.getParameterTypes().length != 1) { + throw new MessagingConfigurationException( + "method must accept exactly one parameter"); + } + String propertyName = (String) attributes.get(PROPERTY_KEY); + String attributeName = (String) attributes.get(ATTRIBUTE_KEY); + Object retval = null; + if (StringUtils.hasText(propertyName)) { + if (StringUtils.hasText(attributeName)) { + throw new MessagingConfigurationException( + "cannot accept both 'property' and 'attribute'"); + } + String property = message.getHeader().getProperty(propertyName); + retval = this.invokeMethod(invoker, property); + } + else if (StringUtils.hasText(attributeName)) { + Object attribute = message.getHeader().getAttribute(attributeName); + retval = this.invokeMethod(invoker, attribute); + } + else { + Class type = method.getParameterTypes()[0]; + if (type.equals(Message.class)) { + retval = this.invokeMethod(invoker, message); + } + retval = this.invokeMethod(invoker, message.getPayload()); + } + if (retval != null) { + if (retval instanceof Collection) { + Collection channels = (Collection) retval; + for (Object channel : channels) { + if (channel instanceof MessageChannel) { + this.sendMessage(message, (MessageChannel) channel); + } + else if (channel instanceof String) { + this.sendMessage(message, (String) channel); + } + else { + throw new MessagingConfigurationException( + "router method must return type 'MessageChannel' or 'String'"); + } + } + } + else if (retval instanceof MessageChannel[]) { + for (MessageChannel channel : (MessageChannel[]) retval) { + this.sendMessage(message, channel); + } + } + else if (retval instanceof String[]) { + for (String channelName : (String[]) retval) { + this.sendMessage(message, channelName); + } + } + else if (retval instanceof MessageChannel) { + this.sendMessage(message, (MessageChannel) retval); + } + else if (retval instanceof String) { + this.sendMessage(message, (String) retval); + } + else { + throw new MessagingConfigurationException( + "router method must return type 'MessageChannel' or 'String'"); + } + } + return null; + } + + private Object invokeMethod(SimpleMethodInvoker invoker, Object parameter) { + if (this.logger.isDebugEnabled()) { + logger.debug("invoking method '" + method.getName() + "' with parameter of type '" + + parameter.getClass().getName() + "'"); + } + return invoker.invokeMethod(parameter); + } + + private boolean sendMessage(Message message, String channelName) { + MessageChannel channel = this.channelRegistry.lookupChannel(channelName); + if (channel == null) { + if (logger.isWarnEnabled()) { + logger.warn("unable to resolve channel for name '" + channelName + "'"); + } + return false; + } + if (logger.isDebugEnabled()) { + logger.debug("sending message to channel '" + channelName + "'"); + } + return this.sendMessage(message, channel); + } + + private boolean sendMessage(Message message, MessageChannel channel) { + if (logger.isDebugEnabled()) { + logger.debug("sending message to channel '" + channel + "'"); + } + return channel.send(message); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/RoutingBarrier.java b/spring-integration-core/src/main/java/org/springframework/integration/router/RoutingBarrier.java new file mode 100644 index 0000000000..8941bd1184 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/RoutingBarrier.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.router; + +/** + * Base strategy interface for barriers. Defines the common requirement of + * routing scenarios that involve waiting for a particular condition to be + * satisfied, such as an {@link Aggregator} or {@link Resequencer}. + * + * @author Mark Fisher + */ +public interface RoutingBarrier { + + boolean checkCondition(T t); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/SingleChannelRouter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/SingleChannelRouter.java new file mode 100644 index 0000000000..7aa81e5fa7 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/SingleChannelRouter.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.router; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.integration.MessagingConfigurationException; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.message.Message; + +/** + * A router implementation for sending to at most one {@link MessageChannel}. + * Requires either a {@link ChannelResolver} or {@link ChannelNameResolver} + * strategy instance. In the case of the latter, the + * {@link org.springframework.integration.channel.ChannelRegistry} reference + * must also be provided. For convenience, the superclass does implement + * {@link org.springframework.integration.channel.ChannelRegistryAware}. + * + * @author Mark Fisher + */ +public class SingleChannelRouter extends AbstractRoutingMessageHandler { + + private ChannelResolver channelResolver; + + private ChannelNameResolver channelNameResolver; + + + public void setChannelResolver(ChannelResolver channelResolver) { + this.channelResolver = channelResolver; + } + + public void setChannelNameResolver(ChannelNameResolver channelNameResolver) { + this.channelNameResolver = channelNameResolver; + } + + @Override + public void validate() { + if (!(this.channelResolver != null ^ this.channelNameResolver != null)) { + throw new MessagingConfigurationException( + "exactly one of 'channelResolver' or 'channelNameResolver' must be provided"); + } + if (this.channelNameResolver != null && this.getChannelRegistry() == null) { + throw new MessagingConfigurationException("'channelRegistry' is required when resolving by channel name"); + } + } + + @Override + public List resolveChannels(Message message) { + List channels = new ArrayList(); + MessageChannel channel = this.resolveChannel(message); + if (channel != null) { + channels.add(channel); + } + return channels; + } + + private MessageChannel resolveChannel(Message message) { + if (this.channelResolver != null) { + return this.channelResolver.resolve(message); + } + if (this.channelNameResolver == null || this.getChannelRegistry() == null) { + throw new MessagingConfigurationException("router configuration requires either " + + "a 'channelResolver' or both 'channelNameResolver' and 'channelRegistry'"); + } + String channelName = this.channelNameResolver.resolve(message); + return this.getChannelRegistry().lookupChannel(channelName); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/Splitter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/Splitter.java new file mode 100644 index 0000000000..2d8510b766 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/Splitter.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.router; + +import java.util.Collection; + +/** + * Base interface for splitters. + * + * @author Mark Fisher + */ +public interface Splitter { + + Collection split(T t); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/SplitterMessageHandlerAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/SplitterMessageHandlerAdapter.java new file mode 100644 index 0000000000..270492cf10 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/SplitterMessageHandlerAdapter.java @@ -0,0 +1,138 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.router; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Map; + +import org.springframework.integration.MessagingConfigurationException; +import org.springframework.integration.annotation.Splitter; +import org.springframework.integration.channel.ChannelRegistry; +import org.springframework.integration.channel.ChannelRegistryAware; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.handler.AbstractMessageHandlerAdapter; +import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.message.Message; +import org.springframework.integration.util.SimpleMethodInvoker; + +/** + * MessageHandler adapter for methods annotated with {@link Splitter @Splitter}. + * + * @author Mark Fisher + */ +public class SplitterMessageHandlerAdapter extends AbstractMessageHandlerAdapter implements ChannelRegistryAware { + + private static final String CHANNEL_KEY = "channel"; + + private Map attributes; + + private Method method; + + private ChannelRegistry channelRegistry; + + + public SplitterMessageHandlerAdapter(Object object, Method method, Map attributes) { + this.setObject(object); + this.method = method; + this.attributes = attributes; + } + + public void setChannelRegistry(ChannelRegistry channelRegistry) { + this.channelRegistry = channelRegistry; + } + + @Override + protected Object doHandle(Message message, SimpleMethodInvoker invoker) { + if (method.getParameterTypes().length != 1) { + throw new MessagingConfigurationException( + "method must accept exactly one parameter"); + } + String channelName = (String) attributes.get(CHANNEL_KEY); + Object retval = null; + Class type = method.getParameterTypes()[0]; + if (type.equals(Message.class)) { + retval = invoker.invokeMethod(message); + } + else if (type.equals(message.getPayload().getClass())) { + retval = invoker.invokeMethod(message.getPayload()); + } + else { + if (logger.isWarnEnabled()) { + logger.warn("Message payload type did not match expected type of Splitter method '" + this.method.getName() + "'"); + } + return message; + } + if (retval == null) { + if (logger.isWarnEnabled()) { + logger.warn("Splitter method '" + this.method.getName() + "' returned null"); + } + return null; + } + if (retval instanceof Collection) { + Collection items = (Collection) retval; + int counter = 0; + for (Object item : items) { + if (item instanceof Message) { + Message splitMessage = (Message) item; + splitMessage.getHeader().setCorrelationId(message.getId()); + this.sendMessage(splitMessage, channelName); + } + else { + Message splitMessage = new GenericMessage(message.getId() + "#" + (counter++), item); + splitMessage.getHeader().setCorrelationId(message.getId()); + this.sendMessage(splitMessage, channelName); + } + } + } + else if (retval.getClass().isArray()) { + int counter = 0; + for (Object item : (Object[]) retval) { + if (item instanceof Message) { + Message splitMessage = (Message) item; + splitMessage.getHeader().setCorrelationId(message.getId()); + this.sendMessage(splitMessage, channelName); + } + else { + Message splitMessage = new GenericMessage(message.getId() + "#" + (counter++), item); + splitMessage.getHeader().setCorrelationId(message.getId()); + this.sendMessage(splitMessage, channelName); + } + } + } + else { + throw new MessagingConfigurationException( + "splitter method must return either a Collection or array"); + } + return null; + } + + private boolean sendMessage(Message message, String channelName) { + MessageChannel channel = this.channelRegistry.lookupChannel(channelName); + if (channel == null) { + if (logger.isWarnEnabled()) { + logger.warn("unable to resolve channel for name '" + channelName + "'"); + } + return false; + } + if (logger.isDebugEnabled()) { + logger.debug("sending message to channel '" + channelName + "'"); + } + return channel.send(message); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/transformer/MessageFilter.java b/spring-integration-core/src/main/java/org/springframework/integration/transformer/MessageFilter.java new file mode 100644 index 0000000000..3247493fe7 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/transformer/MessageFilter.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.transformer; + +import org.springframework.integration.handler.MessageHandler; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessageSelector; + +/** + * Handler for deciding whether to pass a message. Implements + * {@link MessageHandler} and simply delegates to a {@link MessageSelector}. + * + * @author Mark Fisher + */ +public class MessageFilter implements MessageHandler { + + private MessageSelector selector; + + + public MessageFilter(MessageSelector selector) { + this.selector = selector; + } + + public Message handle(Message message) { + if (this.selector.accept(message)) { + return message; + } + return null; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/util/MethodValidator.java b/spring-integration-core/src/main/java/org/springframework/integration/util/MethodValidator.java new file mode 100644 index 0000000000..101cf3141b --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/util/MethodValidator.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.util; + +import java.lang.reflect.Method; + +import org.springframework.integration.MessagingConfigurationException; + +/** + * Interface for method validation. Implementations should throw an exception if + * the method is invalid for its purpose. + * + * @author Mark Fisher + */ +public interface MethodValidator { + + void validate(Method method) throws MessagingConfigurationException; + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/util/RandomGuid.java b/spring-integration-core/src/main/java/org/springframework/integration/util/RandomGuid.java new file mode 100644 index 0000000000..c954667d6a --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/util/RandomGuid.java @@ -0,0 +1,197 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.util; + +/* + * RandomGUID from http://www.javaexchange.com/aboutRandomGUID.html + * @version 1.2.1 11/05/02 @author Marc A. Mnich + * + * From www.JavaExchange.com, Open Software licensing + * + * 11/05/02 -- Performance enhancement from Mike Dubman. Moved InetAddr.getLocal to static block. Mike has measured a 10 + * fold improvement in run time. 01/29/02 -- Bug fix: Improper seeding of nonsecure Random object caused duplicate GUIDs + * to be produced. Random object is now only created once per JVM. 01/19/02 -- Modified random seeding and added new + * constructor to allow secure random feature. 01/14/02 -- Added random function seeding with JVM run time + */ + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Random; + +/** + * Globally unique identifier generator. + *

+ * In the multitude of java GUID generators, I found none that guaranteed randomness. GUIDs are guaranteed to be + * globally unique by using ethernet MACs, IP addresses, time elements, and sequential numbers. GUIDs are not expected + * to be random and most often are easy/possible to guess given a sample from a given generator. SQL Server, for example + * generates GUID that are unique but sequencial within a given instance. + *

+ * GUIDs can be used as security devices to hide things such as files within a filesystem where listings are unavailable + * (e.g. files that are served up from a Web server with indexing turned off). This may be desirable in cases where + * standard authentication is not appropriate. In this scenario, the RandomGuids are used as directories. Another + * example is the use of GUIDs for primary keys in a database where you want to ensure that the keys are secret. Random + * GUIDs can then be used in a URL to prevent hackers (or users) from accessing records by guessing or simply by + * incrementing sequential numbers. + *

+ * There are many other possibilities of using GUIDs in the realm of security and encryption where the element of + * randomness is important. This class was written for these purposes but can also be used as a general purpose GUID + * generator as well. + *

+ * RandomGuid generates truly random GUIDs by using the system's IP address (name/IP), system time in milliseconds (as + * an integer), and a very large random number joined together in a single String that is passed through an MD5 hash. + * The IP address and system time make the MD5 seed globally unique and the random number guarantees that the generated + * GUIDs will have no discernible pattern and cannot be guessed given any number of previously generated GUIDs. It is + * generally not possible to access the seed information (IP, time, random number) from the resulting GUIDs as the MD5 + * hash algorithm provides one way encryption. + *

+ * Security of RandomGuid: RandomGuid can be called one of two ways -- with the basic java Random number + * generator or a cryptographically strong random generator (SecureRandom). The choice is offered because the secure + * random generator takes about 3.5 times longer to generate its random numbers and this performance hit may not be + * worth the added security especially considering the basic generator is seeded with a cryptographically strong random + * seed. + *

+ * Seeding the basic generator in this way effectively decouples the random numbers from the time component making it + * virtually impossible to predict the random number component even if one had absolute knowledge of the System time. + * Thanks to Ashutosh Narhari for the suggestion of using the static method to prime the basic random generator. + *

+ * Using the secure random option, this class complies with the statistical random number generator tests specified in + * FIPS 140-2, Security Requirements for Cryptographic Modules, section 4.9.1. + *

+ * I converted all the pieces of the seed to a String before handing it over to the MD5 hash so that you could print it + * out to make sure it contains the data you expect to see and to give a nice warm fuzzy. If you need better + * performance, you may want to stick to byte[] arrays. + *

+ * I believe that it is important that the algorithm for generating random GUIDs be open for inspection and + * modification. This class is free for all uses. + * + * @version 1.2.1 11/05/02 + * @author Marc A. Mnich + */ +public class RandomGuid { + + private static Random random; + + private static SecureRandom secureRandom; + + private static String id; + + private String guid; + + /* + * Static block to take care of one time secureRandom seed. It takes a few seconds to initialize SecureRandom. You + * might want to consider removing this static block or replacing it with a "time since first loaded" seed to reduce + * this time. This block will run only once per JVM instance. + */ + static { + secureRandom = new SecureRandom(); + long secureInitializer = secureRandom.nextLong(); + random = new Random(secureInitializer); + try { + id = InetAddress.getLocalHost().toString(); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + + /** + * Default constructor. With no specification of security option, this constructor defaults to lower security, high + * performance. + */ + public RandomGuid() { + getRandomGuid(false); + } + + /** + * Constructor with security option. Setting secure true enables each random number generated to be + * cryptographically strong. Secure false defaults to the standard Random function seeded with a single + * cryptographically strong random number. + */ + public RandomGuid(boolean secure) { + getRandomGuid(secure); + } + + /** + * Method to generate the random GUID. + */ + private void getRandomGuid(boolean secure) { + MessageDigest md5 = null; + StringBuffer sbValueBeforeMD5 = new StringBuffer(); + + try { + md5 = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + + long time = System.currentTimeMillis(); + long rand = 0; + + if (secure) { + rand = secureRandom.nextLong(); + } else { + rand = random.nextLong(); + } + + // This StringBuffer can be as long as you need; the MD5 + // hash will always return 128 bits. You can change + // the seed to include anything you want here. + // You could even stream a file through the MD5 making + // the odds of guessing it at least as great as that + // of guessing the contents of the file! + sbValueBeforeMD5.append(id); + sbValueBeforeMD5.append(":"); + sbValueBeforeMD5.append(Long.toString(time)); + sbValueBeforeMD5.append(":"); + sbValueBeforeMD5.append(Long.toString(rand)); + + String valueBeforeMD5 = sbValueBeforeMD5.toString(); + md5.update(valueBeforeMD5.getBytes()); + + byte[] array = md5.digest(); + StringBuffer sb = new StringBuffer(); + for (int j = 0; j < array.length; ++j) { + int b = array[j] & 0xFF; + if (b < 0x10) + sb.append('0'); + sb.append(Integer.toHexString(b)); + } + guid = sb.toString(); + } + + /** + * Convert to the standard format for GUID (Useful for SQL Server UniqueIdentifiers, etc). Example: + * "C2FEEEAC-CFCD-11D1-8B05-00600806D9B6". + */ + public String toString() { + String raw = guid.toUpperCase(); + StringBuffer sb = new StringBuffer(); + sb.append(raw.substring(0, 8)); + sb.append("-"); + sb.append(raw.substring(8, 12)); + sb.append("-"); + sb.append(raw.substring(12, 16)); + sb.append("-"); + sb.append(raw.substring(16, 20)); + sb.append("-"); + sb.append(raw.substring(20)); + return sb.toString(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/util/RandomGuidUidGenerator.java b/spring-integration-core/src/main/java/org/springframework/integration/util/RandomGuidUidGenerator.java new file mode 100644 index 0000000000..a4700c138f --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/util/RandomGuidUidGenerator.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.util; + +import java.io.Serializable; + +/** + * An id generator that uses the RandomGuid support class. The default + * implementation used by the integration system. + * + * @author Keith Donald + */ +@SuppressWarnings("serial") +public class RandomGuidUidGenerator implements UidGenerator, Serializable { + + /** + * Should the random GUID generated be secure? + */ + private boolean secure; + + /** + * Returns whether or not the generated random numbers are secure, + * meaning cryptographically strong. + */ + public boolean isSecure() { + return secure; + } + + /** + * Sets whether or not the generated random numbers should be secure. + * If set to true, generated GUIDs are cryptographically strong. + */ + public void setSecure(boolean secure) { + this.secure = secure; + } + + public Serializable generateUid() { + return new RandomGuid(secure).toString(); + } + + public Serializable parseUid(String encodedUid) { + return encodedUid; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/util/SimpleMethodInvoker.java b/spring-integration-core/src/main/java/org/springframework/integration/util/SimpleMethodInvoker.java new file mode 100644 index 0000000000..1c480ef88b --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/util/SimpleMethodInvoker.java @@ -0,0 +1,74 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.util; + +import java.lang.reflect.InvocationTargetException; + +import org.springframework.integration.MessageDeliveryException; +import org.springframework.util.Assert; +import org.springframework.util.MethodInvoker; +import org.springframework.util.ObjectUtils; + +/** + * A simple wrapper for {@link MethodInvoker}. + * + * @author Mark Fisher + */ +public class SimpleMethodInvoker { + + private T object; + + private String method; + + private MethodValidator methodValidator; + + + public SimpleMethodInvoker(T object, String method) { + Assert.notNull(object, "'object' must not be null"); + Assert.notNull(method, "'method' must not be null"); + this.object = object; + this.method = method; + } + + public void setMethodValidator(MethodValidator methodValidator) { + this.methodValidator = methodValidator; + } + + public Object invokeMethod(Object ... args) { + try { + MethodInvoker methodInvoker = new MethodInvoker(); + methodInvoker.setTargetObject(this.object); + methodInvoker.setTargetMethod(this.method); + methodInvoker.setArguments(args); + methodInvoker.prepare(); + methodInvoker.getPreparedMethod().setAccessible(true); + if (this.methodValidator != null) { + this.methodValidator.validate(methodInvoker.getPreparedMethod()); + } + return methodInvoker.invoke(); + } + catch (InvocationTargetException e) { + throw new MessageDeliveryException( + "Method '" + this.method + "' threw exception", e.getTargetException()); + } + catch (Throwable e) { + throw new MessageDeliveryException("Failed to invoke method '" + this.method + + "' with arguments " + ObjectUtils.nullSafeToString(args), e); + } + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/util/UidGenerator.java b/spring-integration-core/src/main/java/org/springframework/integration/util/UidGenerator.java new file mode 100644 index 0000000000..858e7529d2 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/util/UidGenerator.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.util; + +import java.io.Serializable; + +/** + * A strategy for generating ids to uniquely identify integration artifacts such + * as Messages. + * + * @author Keith Donald + */ +public interface UidGenerator { + + /** + * Generate a new unique id. + * @return a serializable id, guaranteed to be unique in some context + */ + public Serializable generateUid(); + + /** + * Convert the string-encoded uid into its original object form. + * @param encodedUid the string encoded uid + * @return the converted uid + */ + public Serializable parseUid(String encodedUid); + +} diff --git a/spring-integration-core/src/test/java/log4j.properties b/spring-integration-core/src/test/java/log4j.properties new file mode 100644 index 0000000000..c5000b932a --- /dev/null +++ b/spring-integration-core/src/test/java/log4j.properties @@ -0,0 +1,7 @@ +log4j.rootCategory=WARN, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%c{1}: %m%n + +log4j.category.org.springframework.integration=INFO \ No newline at end of file diff --git a/spring-integration-core/src/test/java/org/springframework/integration/adapter/AdapterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/adapter/AdapterTests.java new file mode 100644 index 0000000000..904a1bcfa4 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/adapter/AdapterTests.java @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.io.IOException; + +import org.junit.Test; + +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @author Mark Fisher + */ +public class AdapterTests { + + @Test + public void testAdaptersWithBeanDefinitions() throws IOException, InterruptedException { + AbstractApplicationContext context = new ClassPathXmlApplicationContext("adapterTests.xml", this.getClass()); + TestSink sink = (TestSink) context.getBean("sink"); + assertNull(sink.get()); + context.start(); + String result = null; + int attempts = 0; + while (result == null && attempts++ < 100) { + Thread.sleep(5); + result = sink.get(); + } + assertNotNull(result); + context.close(); + } + + @Test + public void testAdaptersWithNamespace() throws IOException, InterruptedException { + AbstractApplicationContext context = new ClassPathXmlApplicationContext("adapterTestsWithNamespace.xml", this.getClass()); + TestSink sink = (TestSink) context.getBean("sink"); + assertNull(sink.get()); + context.start(); + String result = null; + int attempts = 0; + while (result == null && attempts++ < 100) { + Thread.sleep(5); + result = sink.get(); + } + assertNotNull(result); + context.close(); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/adapter/DefaultTargetAdapterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/adapter/DefaultTargetAdapterTests.java new file mode 100644 index 0000000000..3bcd0637c8 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/adapter/DefaultTargetAdapterTests.java @@ -0,0 +1,82 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import org.springframework.integration.bus.MessageBus; +import org.springframework.integration.channel.PointToPointChannel; +import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.message.Message; + +/** + * @author Mark Fisher + */ +public class DefaultTargetAdapterTests { + + @Test + public void testAdapterSendsToChannel() throws Exception { + SynchronousQueue queue = new SynchronousQueue(); + TestBean testBean = new TestBean(queue); + MethodInvokingTarget target = new MethodInvokingTarget(); + target.setObject(testBean); + target.setMethod("foo"); + target.afterPropertiesSet(); + DefaultTargetAdapter adapter = new DefaultTargetAdapter(target); + PointToPointChannel channel = new PointToPointChannel(); + adapter.setChannel(channel); + Message message = new GenericMessage("123", "testing"); + channel.send(message); + assertNull(queue.poll()); + MessageBus bus = new MessageBus(); + bus.registerChannel("channel", channel); + bus.registerTargetAdapter("targetAdapter", adapter); + bus.start(); + String result = queue.poll(100, TimeUnit.MILLISECONDS); + assertNotNull(result); + assertEquals("testing", result); + bus.stop(); + } + + + public static class TestBean { + + private BlockingQueue queue; + + public TestBean(BlockingQueue queue) { + this.queue = queue; + } + + public void foo(String s) { + try { + this.queue.put(s); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/adapter/MethodInvokingSourceTests.java b/spring-integration-core/src/test/java/org/springframework/integration/adapter/MethodInvokingSourceTests.java new file mode 100644 index 0000000000..9b8f97d4a1 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/adapter/MethodInvokingSourceTests.java @@ -0,0 +1,82 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.Collection; + +import org.junit.Test; + +import org.springframework.integration.MessageDeliveryException; + +/** + * @author Mark Fisher + */ +public class MethodInvokingSourceTests { + + @Test + public void testValidMethod() { + MethodInvokingSource source = new MethodInvokingSource(); + source.setObject(new TestBean()); + source.setMethod("validMethod"); + Collection result = source.poll(5); + assertNotNull(result); + assertEquals("valid", result.iterator().next()); + } + + @Test(expected=MessageDeliveryException.class) + public void testNoMatchingMethodName() { + MethodInvokingSource source = new MethodInvokingSource(); + source.setObject(new TestBean()); + source.setMethod("noMatchingMethod"); + source.poll(5); + } + + @Test(expected=MessageDeliveryException.class) + public void testInvalidMethodWithArg() { + MethodInvokingSource source = new MethodInvokingSource(); + source.setObject(new TestBean()); + source.setMethod("invalidMethodWithArg"); + source.poll(5); + } + + @Test(expected=MessageDeliveryException.class) + public void testInvalidMethodWithNoReturnValue() { + MethodInvokingSource source = new MethodInvokingSource(); + source.setObject(new TestBean()); + source.setMethod("invalidMethodWithNoReturnValue"); + source.poll(5); + } + + + private static class TestBean { + + public String validMethod() { + return "valid"; + } + + public String invalidMethodWithArg(String arg) { + return "invalid"; + } + + public void invalidMethodWithNoReturnValue() { + } + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/adapter/MethodInvokingTargetTests.java b/spring-integration-core/src/test/java/org/springframework/integration/adapter/MethodInvokingTargetTests.java new file mode 100644 index 0000000000..ce5e7ba86b --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/adapter/MethodInvokingTargetTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import org.springframework.integration.MessageDeliveryException; + +/** + * @author Mark Fisher + */ +public class MethodInvokingTargetTests { + + @Test + public void testValidMethod() { + MethodInvokingTarget target = new MethodInvokingTarget(); + target.setObject(new TestSink()); + target.setMethod("validMethod"); + target.afterPropertiesSet(); + boolean result = target.send("test"); + assertTrue(result); + } + + @Test(expected=MessageDeliveryException.class) + public void testInvalidMethodWithNoArgs() { + MethodInvokingTarget target = new MethodInvokingTarget(); + target.setObject(new TestSink()); + target.setMethod("invalidMethodWithNoArgs"); + target.afterPropertiesSet(); + target.send("test"); + } + + @Test + public void testValidMethodWithIgnoredReturnValue() { + MethodInvokingTarget target = new MethodInvokingTarget(); + target.setObject(new TestSink()); + target.setMethod("validMethodWithIgnoredReturnValue"); + target.afterPropertiesSet(); + boolean result = target.send("test"); + assertTrue(result); + } + + @Test(expected=MessageDeliveryException.class) + public void testNoMatchingMethodName() { + MethodInvokingTarget target = new MethodInvokingTarget(); + target.setObject(new TestSink()); + target.setMethod("noSuchMethod"); + target.afterPropertiesSet(); + target.send("test"); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/adapter/PollingSourceAdapterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/adapter/PollingSourceAdapterTests.java new file mode 100644 index 0000000000..bb7fe6b283 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/adapter/PollingSourceAdapterTests.java @@ -0,0 +1,129 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import org.springframework.integration.MessageHandlingException; +import org.springframework.integration.channel.PointToPointChannel; +import org.springframework.integration.message.Message; + +/** + * @author Mark Fisher + */ +public class PollingSourceAdapterTests { + + @Test + public void testPolledSourceSendsToChannel() { + TestSource source = new TestSource("testing", 1); + PointToPointChannel channel = new PointToPointChannel(); + PollingSourceAdapter adapter = new PollingSourceAdapter(source); + adapter.setChannel(channel); + adapter.setPeriod(100); + adapter.dispatch(); + Message message = channel.receive(); + assertNotNull("message should not be null", message); + assertEquals("testing.1", message.getPayload()); + } + + @Test + public void testSendTimeout() { + TestSource source = new TestSource("testing", 1); + PointToPointChannel channel = new PointToPointChannel(1); + PollingSourceAdapter adapter = new PollingSourceAdapter(source); + adapter.setChannel(channel); + adapter.setPeriod(500); + adapter.setSendTimeout(10); + adapter.dispatch(); + adapter.dispatch(); + Message message1 = channel.receive(); + assertNotNull("message should not be null", message1); + assertEquals("testing.1", message1.getPayload()); + Message message2 = channel.receive(0); + assertNull("second message should be null", message2); + adapter.dispatch(); + Message message3 = channel.receive(0); + assertNotNull("third message should not be null", message3); + assertEquals("testing.3", message3.getPayload()); + } + + @Test + public void testMultipleMessagesPerPoll() { + TestSource source = new TestSource("testing", 3); + PointToPointChannel channel = new PointToPointChannel(); + PollingSourceAdapter adapter = new PollingSourceAdapter(source); + adapter.setChannel(channel); + adapter.setPeriod(1000); + adapter.setMaxMessagesPerTask(5); + adapter.dispatch(); + Message message1 = channel.receive(0); + assertNotNull("message should not be null", message1); + assertEquals("testing.1", message1.getPayload()); + Message message2 = channel.receive(0); + assertNotNull("message should not be null", message2); + assertEquals("testing.2", message2.getPayload()); + Message message3 = channel.receive(0); + assertNotNull("message should not be null", message3); + assertEquals("testing.3", message3.getPayload()); + Message message4 = channel.receive(0); + assertNull("message should be null", message4); + } + + @Test(expected=MessageHandlingException.class) + public void testResultSizeExceedsLimit() { + TestSource source = new TestSource("testing", 3); + PointToPointChannel channel = new PointToPointChannel(); + PollingSourceAdapter adapter = new PollingSourceAdapter(source); + adapter.setChannel(channel); + adapter.setPeriod(1000); + adapter.setMaxMessagesPerTask(2); + adapter.dispatch(); + } + + + private static class TestSource implements PollableSource { + + private String message; + + private int messagesPerPoll; + + private AtomicInteger count = new AtomicInteger(); + + public TestSource(String message, int messagesPerPoll) { + this.message = message; + this.messagesPerPoll = messagesPerPoll; + } + + public Collection poll(int limit) { + List results = new ArrayList(this.messagesPerPoll); + for (int i = 0; i < this.messagesPerPoll; i++) { + results.add(message + "." + count.incrementAndGet()); + } + return results; + } + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/adapter/TestSink.java b/spring-integration-core/src/test/java/org/springframework/integration/adapter/TestSink.java new file mode 100644 index 0000000000..e7953172d0 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/adapter/TestSink.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter; + +/** + * @author Mark Fisher + */ +public class TestSink { + + private String result; + + public void validMethod(String s) { + } + + public void invalidMethodWithNoArgs() { + } + + public String validMethodWithIgnoredReturnValue(String s) { + return "ignored"; + } + + public void store(String s) { + this.result = s; + } + + public String get() { + return this.result; + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/adapter/TestSource.java b/spring-integration-core/src/test/java/org/springframework/integration/adapter/TestSource.java new file mode 100644 index 0000000000..da6cafc459 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/adapter/TestSource.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter; + +/** + * @author Mark Fisher + */ +public class TestSource { + + private boolean fooCalled; + + public String validMethod() { + return "valid"; + } + + public String invalidMethodWithArg(String arg) { + return "invalid"; + } + + public void invalidMethodWithNoReturnValue() { + } + + public String foo() { + if (this.fooCalled) { + try { + Thread.sleep(5000); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + this.fooCalled = true; + return "bar"; + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/adapter/adapterTests.xml b/spring-integration-core/src/test/java/org/springframework/integration/adapter/adapterTests.xml new file mode 100644 index 0000000000..0da1a1ccd5 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/adapter/adapterTests.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/adapter/adapterTestsWithNamespace.xml b/spring-integration-core/src/test/java/org/springframework/integration/adapter/adapterTestsWithNamespace.xml new file mode 100644 index 0000000000..52322d548d --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/adapter/adapterTestsWithNamespace.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/adapter/event/ApplicationEventSourceAdapterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/adapter/event/ApplicationEventSourceAdapterTests.java new file mode 100644 index 0000000000..b1d20b25fe --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/adapter/event/ApplicationEventSourceAdapterTests.java @@ -0,0 +1,115 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter.event; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +import org.springframework.context.ApplicationEvent; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.ContextStartedEvent; +import org.springframework.context.event.ContextStoppedEvent; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.channel.PointToPointChannel; +import org.springframework.integration.message.Message; + +/** + * @author Mark Fisher + */ +public class ApplicationEventSourceAdapterTests { + + @Test + public void testAnyApplicationEventSentByDefault() { + MessageChannel channel = new PointToPointChannel(); + ApplicationEventSourceAdapter adapter = new ApplicationEventSourceAdapter(); + adapter.setChannel(channel); + Message message1 = channel.receive(0); + assertNull(message1); + adapter.onApplicationEvent(new TestApplicationEvent1()); + adapter.onApplicationEvent(new TestApplicationEvent2()); + Message message2 = channel.receive(20); + assertNotNull(message2); + assertEquals("event1", ((ApplicationEvent) message2.getPayload()).getSource()); + Message message3 = channel.receive(20); + assertNotNull(message3); + assertEquals("event2", ((ApplicationEvent) message3.getPayload()).getSource()); + } + + @Test + public void testOnlyConfiguredEventTypesAreSent() { + MessageChannel channel = new PointToPointChannel(); + ApplicationEventSourceAdapter adapter = new ApplicationEventSourceAdapter(); + List> eventTypes = new ArrayList>(); + eventTypes.add(TestApplicationEvent1.class); + adapter.setEventTypes(eventTypes); + adapter.setChannel(channel); + Message message1 = channel.receive(0); + assertNull(message1); + adapter.onApplicationEvent(new TestApplicationEvent1()); + adapter.onApplicationEvent(new TestApplicationEvent2()); + Message message2 = channel.receive(20); + assertNotNull(message2); + assertEquals("event1", ((ApplicationEvent) message2.getPayload()).getSource()); + Message message3 = channel.receive(0); + assertNull(message3); + } + + @Test + public void testApplicationContextEvents() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationEventSourceAdapterTests.xml", this.getClass()); + MessageChannel channel = (MessageChannel) context.getBean("channel"); + Message refreshedEventMessage = channel.receive(0); + assertNotNull(refreshedEventMessage); + assertEquals(ContextRefreshedEvent.class, refreshedEventMessage.getPayload().getClass()); + context.start(); + Message startedEventMessage = channel.receive(0); + assertNotNull(startedEventMessage); + assertEquals(ContextStartedEvent.class, startedEventMessage.getPayload().getClass()); + context.close(); + Message closedEventMessage = channel.receive(0); + assertNotNull(closedEventMessage); + assertEquals(ContextClosedEvent.class, closedEventMessage.getPayload().getClass()); + Message stoppedEventMessage = channel.receive(0); + assertNotNull(stoppedEventMessage); + assertEquals(ContextStoppedEvent.class, stoppedEventMessage.getPayload().getClass()); + } + + + private static class TestApplicationEvent1 extends ApplicationEvent { + + public TestApplicationEvent1() { + super("event1"); + } + } + + + private static class TestApplicationEvent2 extends ApplicationEvent { + + public TestApplicationEvent2() { + super("event2"); + } + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/adapter/event/ApplicationEventTargetAdapterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/adapter/event/ApplicationEventTargetAdapterTests.java new file mode 100644 index 0000000000..89b2d24126 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/adapter/event/ApplicationEventTargetAdapterTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter.event; + +import static org.junit.Assert.assertEquals; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.integration.bus.MessageBus; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.channel.PointToPointChannel; +import org.springframework.integration.message.StringMessage; + +/** + * @author Mark Fisher + */ +public class ApplicationEventTargetAdapterTests { + + @Test + public void testSendingEvent() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + ApplicationEventPublisher publisher = new ApplicationEventPublisher() { + public void publishEvent(ApplicationEvent event) { + latch.countDown(); + } + }; + MessageChannel channel = new PointToPointChannel(); + ApplicationEventTargetAdapter adapter = new ApplicationEventTargetAdapter(); + adapter.setApplicationEventPublisher(publisher); + adapter.setChannel(channel); + MessageBus bus = new MessageBus(); + bus.registerChannel("channel", channel); + bus.registerTargetAdapter("adapter", adapter); + bus.start(); + assertEquals(1, latch.getCount()); + channel.send(new StringMessage("123", "testing")); + latch.await(100, TimeUnit.MILLISECONDS); + assertEquals(0, latch.getCount()); + bus.stop(); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/adapter/event/applicationEventSourceAdapterTests.xml b/spring-integration-core/src/test/java/org/springframework/integration/adapter/event/applicationEventSourceAdapterTests.xml new file mode 100644 index 0000000000..26f6ce8098 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/adapter/event/applicationEventSourceAdapterTests.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/adapter/file/DefaultFileNameGeneratorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/adapter/file/DefaultFileNameGeneratorTests.java new file mode 100644 index 0000000000..adf525f8ca --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/adapter/file/DefaultFileNameGeneratorTests.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.adapter.file; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.message.Message; + +/** + * @author Mark Fisher + */ +public class DefaultFileNameGeneratorTests { + + @Test + public void testWithFileNamePropertyProvided() { + Message message = new GenericMessage("123", "testing"); + message.getHeader().setProperty(FileNameGenerator.FILENAME_PROPERTY_KEY, "foo.bar"); + FileNameGenerator generator = new DefaultFileNameGenerator(); + String filename = generator.generateFileName(message); + assertEquals("foo.bar", filename); + } + + @Test + public void testWithoutFileNamePropertyProvided() { + Message message = new GenericMessage("123", "testing"); + FileNameGenerator generator = new DefaultFileNameGenerator(); + String filename = generator.generateFileName(message); + assertTrue(filename.startsWith("123-")); + assertTrue(filename.endsWith(".msg")); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/aop/ITestBean.java b/spring-integration-core/src/test/java/org/springframework/integration/aop/ITestBean.java new file mode 100644 index 0000000000..1a8fbd71eb --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/aop/ITestBean.java @@ -0,0 +1,26 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.aop; + +/** + * @author Mark Fisher + */ +public interface ITestBean { + + String test(); + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/aop/MessagePublishingInterceptorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/aop/MessagePublishingInterceptorTests.java new file mode 100644 index 0000000000..f67e434d1e --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/aop/MessagePublishingInterceptorTests.java @@ -0,0 +1,101 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.aop; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import org.junit.Test; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.channel.PointToPointChannel; +import org.springframework.integration.message.Message; + +/** + * @author Mark Fisher + */ +public class MessagePublishingInterceptorTests { + + @Test + public void testNonNullReturnValuePublishedWithDefaultChannel() { + MessageChannel channel = new PointToPointChannel(); + MessagePublishingInterceptor interceptor = new MessagePublishingInterceptor(); + interceptor.setDefaultChannel(channel); + TestService proxy = (TestService) this.createProxy(new TestServiceImpl("hello world"), interceptor); + proxy.messageTest(); + Message message = channel.receive(0); + assertNotNull(message); + assertEquals("hello world", message.getPayload()); + } + + @Test + public void testNullReturnValueNotPublished() { + MessageChannel channel = new PointToPointChannel(); + MessagePublishingInterceptor interceptor = new MessagePublishingInterceptor(); + interceptor.setDefaultChannel(channel); + TestService proxy = (TestService) this.createProxy(new TestServiceImpl(null), interceptor); + proxy.messageTest(); + Message message = channel.receive(0); + assertNull(message); + } + + @Test + public void testVoidReturnValueNotPublished() { + MessageChannel channel = new PointToPointChannel(); + MessagePublishingInterceptor interceptor = new MessagePublishingInterceptor(); + interceptor.setDefaultChannel(channel); + TestService proxy = (TestService) this.createProxy(new TestServiceImpl(null), interceptor); + proxy.voidTest(); + Message message = channel.receive(0); + assertNull(message); + } + + + private Object createProxy(Object target, MessagePublishingInterceptor interceptor) { + ProxyFactory factory = new ProxyFactory(target); + factory.addAdvice(interceptor); + return factory.getProxy(); + } + + + private static interface TestService { + String messageTest(); + void voidTest(); + } + + + private static class TestServiceImpl implements TestService { + + private String message; + + public TestServiceImpl(String message) { + this.message = message; + } + + public String messageTest() { + return this.message; + } + + public void voidTest() { + return; + } + + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/aop/PublisherAnnotationAdvisorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/aop/PublisherAnnotationAdvisorTests.java new file mode 100644 index 0000000000..13b46a2862 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/aop/PublisherAnnotationAdvisorTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.aop; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import org.junit.Test; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.integration.annotation.Publisher; +import org.springframework.integration.channel.ChannelRegistry; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.channel.PointToPointChannel; +import org.springframework.integration.channel.DefaultChannelRegistry; +import org.springframework.integration.message.Message; + +/** + * @author Mark Fisher + */ +public class PublisherAnnotationAdvisorTests { + + @Test + public void testPublisherAnnotation() { + final MessageChannel channel = new PointToPointChannel(); + ChannelRegistry channelRegistry = new DefaultChannelRegistry(); + channelRegistry.registerChannel("testChannel", channel); + PublisherAnnotationAdvisor advisor = new PublisherAnnotationAdvisor(channelRegistry); + TestService proxy = (TestService) this.createProxy(new TestServiceImpl("hello world"), advisor); + proxy.publisherTest(); + Message message = channel.receive(0); + assertNotNull(message); + assertEquals("hello world", message.getPayload()); + } + + @Test + public void testNoPublisherAnnotation() { + final MessageChannel channel = new PointToPointChannel(); + ChannelRegistry channelRegistry = new DefaultChannelRegistry(); + channelRegistry.registerChannel("testChannel", channel); + PublisherAnnotationAdvisor advisor = new PublisherAnnotationAdvisor(channelRegistry); + TestService proxy = (TestService) this.createProxy(new TestServiceImpl("hello world"), advisor); + proxy.noPublisherTest(); + Message message = channel.receive(0); + assertNull(message); + } + + + private Object createProxy(Object target, PublisherAnnotationAdvisor advisor) { + ProxyFactory factory = new ProxyFactory(target); + factory.addAdvisor(advisor); + return factory.getProxy(); + } + + + private static interface TestService { + + String publisherTest(); + + String noPublisherTest(); + } + + + private static class TestServiceImpl implements TestService { + + private String message; + + public TestServiceImpl(String message) { + this.message = message; + } + + @Publisher(channel="testChannel") + public String publisherTest() { + return this.message; + } + + public String noPublisherTest() { + return this.message; + } + + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/aop/PublisherAnnotationPostProcessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/aop/PublisherAnnotationPostProcessorTests.java new file mode 100644 index 0000000000..933e11ecfb --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/aop/PublisherAnnotationPostProcessorTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.aop; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.message.Message; + +/** + * @author Mark Fisher + */ +public class PublisherAnnotationPostProcessorTests { + + @Test + public void testPublisherAnnotation() { + ApplicationContext context = new ClassPathXmlApplicationContext( + "publisherAnnotationPostProcessorTests.xml", this.getClass()); + ITestBean testBean = (ITestBean) context.getBean("testBean"); + testBean.test(); + MessageChannel channel = (MessageChannel) context.getBean("testChannel"); + Message result = channel.receive(); + assertEquals("test", result.getPayload()); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/aop/PublisherAnnotationTestBean.java b/spring-integration-core/src/test/java/org/springframework/integration/aop/PublisherAnnotationTestBean.java new file mode 100644 index 0000000000..2470e05845 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/aop/PublisherAnnotationTestBean.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.aop; + +import org.springframework.integration.annotation.Publisher; + + +/** + * @author Mark Fisher + */ +public class PublisherAnnotationTestBean implements ITestBean { + + @Publisher(channel="testChannel") + public String test() { + return "test"; + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/aop/publisherAnnotationPostProcessorTests.xml b/spring-integration-core/src/test/java/org/springframework/integration/aop/publisherAnnotationPostProcessorTests.xml new file mode 100644 index 0000000000..da37c291b8 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/aop/publisherAnnotationPostProcessorTests.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/bus/FixedDelayConsumerTests.java b/spring-integration-core/src/test/java/org/springframework/integration/bus/FixedDelayConsumerTests.java new file mode 100644 index 0000000000..8b4bef0a51 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/bus/FixedDelayConsumerTests.java @@ -0,0 +1,109 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.bus; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; +import org.springframework.integration.channel.PointToPointChannel; +import org.springframework.integration.endpoint.GenericMessageEndpoint; +import org.springframework.integration.endpoint.MessageEndpoint; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.GenericMessage; + +/** + * @author Mark Fisher + */ +public class FixedDelayConsumerTests { + + @Test + public void testAllSentMessagesAreReceivedWithinTimeLimit() throws Exception { + int messagesToSend = 20; + final AtomicInteger counter = new AtomicInteger(0); + final CountDownLatch latch = new CountDownLatch(messagesToSend); + PointToPointChannel channel = new PointToPointChannel(); + MessageEndpoint endpoint = new GenericMessageEndpoint() { + public void messageReceived(Message message) { + counter.incrementAndGet(); + latch.countDown(); + } + }; + MessageBus bus = new MessageBus(); + bus.registerChannel("testChannel", channel); + bus.registerEndpoint("testEndpoint", endpoint); + ConsumerPolicy policy = new ConsumerPolicy(); + policy.setConcurrency(1); + policy.setMaxConcurrency(1); + policy.setMaxMessagesPerTask(1); + policy.setFixedRate(true); + policy.setPeriod(10); + Subscription subscription = new Subscription(); + subscription.setChannel("testChannel"); + subscription.setEndpoint("testEndpoint"); + subscription.setPolicy(policy); + bus.activateSubscription(subscription); + bus.start(); + for (int i = 0; i < messagesToSend; i++) { + channel.send(new GenericMessage(1, "test " + (i+1))); + } + latch.await(1000, TimeUnit.MILLISECONDS); + assertEquals(messagesToSend, counter.get()); + } + + @Test + public void testTimedOutMessagesAreNotReceived() throws Exception { + int messagesToSend = 20; + final AtomicInteger counter = new AtomicInteger(0); + final CountDownLatch latch = new CountDownLatch(messagesToSend); + PointToPointChannel channel = new PointToPointChannel(); + MessageEndpoint endpoint = new GenericMessageEndpoint() { + public void messageReceived(Message message) { + counter.incrementAndGet(); + latch.countDown(); + } + }; + MessageBus bus = new MessageBus(); + bus.registerChannel("testChannel", channel); + bus.registerEndpoint("testEndpoint", endpoint); + ConsumerPolicy policy = new ConsumerPolicy(); + policy.setConcurrency(1); + policy.setMaxConcurrency(1); + policy.setMaxMessagesPerTask(1); + policy.setFixedRate(true); + policy.setPeriod(10); + Subscription subscription = new Subscription(); + subscription.setChannel("testChannel"); + subscription.setEndpoint("testEndpoint"); + subscription.setPolicy(policy); + bus.activateSubscription(subscription); + for (int i = 0; i < messagesToSend; i++) { + channel.send(new GenericMessage(1, "test " + (i+1))); + } + bus.start(); + latch.await(80, TimeUnit.MILLISECONDS); + int count = counter.get(); + assertTrue("received " + count + ", expected less than 10", counter.get() < 10); + assertTrue("received " + count + ", expected more than 7", counter.get() > 7); + bus.stop(); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/bus/FixedRateConsumerTests.java b/spring-integration-core/src/test/java/org/springframework/integration/bus/FixedRateConsumerTests.java new file mode 100644 index 0000000000..2a347a5d0c --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/bus/FixedRateConsumerTests.java @@ -0,0 +1,106 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.bus; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import org.springframework.integration.channel.PointToPointChannel; +import org.springframework.integration.endpoint.GenericMessageEndpoint; +import org.springframework.integration.endpoint.MessageEndpoint; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.GenericMessage; + +/** + * @author Mark Fisher + */ +public class FixedRateConsumerTests { + + @Test + public void testAllSentMessagesAreReceivedWithinTimeLimit() throws Exception { + int messagesToSend = 20; + final AtomicInteger counter = new AtomicInteger(0); + final CountDownLatch latch = new CountDownLatch(messagesToSend); + PointToPointChannel channel = new PointToPointChannel(); + MessageEndpoint endpoint = new GenericMessageEndpoint() { + public void messageReceived(Message message) { + counter.incrementAndGet(); + latch.countDown(); + } + }; + MessageBus bus = new MessageBus(); + bus.registerChannel("testChannel", channel); + bus.registerEndpoint("testEndpoint", endpoint); + ConsumerPolicy policy = new ConsumerPolicy(); + policy.setFixedRate(true); + policy.setPeriod(10); + Subscription subscription = new Subscription(); + subscription.setChannel("testChannel"); + subscription.setEndpoint("testEndpoint"); + subscription.setPolicy(policy); + bus.activateSubscription(subscription); + bus.start(); + for (int i = 0; i < messagesToSend; i++) { + channel.send(new GenericMessage(1, "test " + (i+1))); + } + latch.await(210, TimeUnit.MILLISECONDS); + assertEquals(messagesToSend, counter.get()); + } + + @Test + public void testTimedOutMessagesAreNotReceived() throws Exception { + int messagesToSend = 20; + final AtomicInteger counter = new AtomicInteger(0); + final CountDownLatch latch = new CountDownLatch(messagesToSend); + PointToPointChannel channel = new PointToPointChannel(); + MessageEndpoint endpoint = new GenericMessageEndpoint() { + public void messageReceived(Message message) { + counter.incrementAndGet(); + latch.countDown(); + } + }; + MessageBus bus = new MessageBus(); + bus.registerChannel("testChannel", channel); + bus.registerEndpoint("testEndpoint", endpoint); + ConsumerPolicy policy = new ConsumerPolicy(); + policy.setConcurrency(1); + policy.setMaxConcurrency(1); + policy.setMaxMessagesPerTask(1); + policy.setFixedRate(true); + policy.setPeriod(20); + Subscription subscription = new Subscription(); + subscription.setChannel("testChannel"); + subscription.setEndpoint("testEndpoint"); + subscription.setPolicy(policy); + bus.activateSubscription(subscription); + bus.start(); + for (int i = 0; i < messagesToSend; i++) { + channel.send(new GenericMessage(1, "test " + (i+1))); + } + latch.await(80, TimeUnit.MILLISECONDS); + int count = counter.get(); + assertTrue("received " + count + ", but expected less than 7", count < 7); + assertTrue("received " + count + ", but expected more than 3", count > 3); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/bus/MessageBusTests.java b/spring-integration-core/src/test/java/org/springframework/integration/bus/MessageBusTests.java new file mode 100644 index 0000000000..4091e70d20 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/bus/MessageBusTests.java @@ -0,0 +1,113 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.bus; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.channel.PointToPointChannel; +import org.springframework.integration.endpoint.GenericMessageEndpoint; +import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.StringMessage; + +/** + * @author Mark Fisher + */ +public class MessageBusTests { + + @Test + public void testChannelsConnectedWithEndpoint() { + MessageBus bus = new MessageBus(); + MessageChannel sourceChannel = new PointToPointChannel(); + MessageChannel targetChannel = new PointToPointChannel(); + bus.registerChannel("sourceChannel", sourceChannel); + sourceChannel.send(new StringMessage("123", "test")); + bus.registerChannel("targetChannel", targetChannel); + GenericMessageEndpoint endpoint = new GenericMessageEndpoint(); + endpoint.setInputChannelName("sourceChannel"); + endpoint.setDefaultOutputChannelName("targetChannel"); + bus.registerEndpoint("endpoint", endpoint); + bus.start(); + Message result = targetChannel.receive(100); + assertEquals("test", result.getPayload()); + bus.stop(); + } + + @Test + public void testChannelsWithoutEndpoint() { + MessageBus bus = new MessageBus(); + MessageChannel sourceChannel = new PointToPointChannel(); + sourceChannel.send(new StringMessage("123", "test")); + MessageChannel targetChannel = new PointToPointChannel(); + bus.registerChannel("sourceChannel", sourceChannel); + bus.registerChannel("targetChannel", targetChannel); + bus.start(); + Message result = targetChannel.receive(100); + assertNull(result); + bus.stop(); + } + + @Test + public void testAutodetectionWithApplicationContext() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("messageBusTests.xml", this.getClass()); + context.start(); + MessageChannel sourceChannel = (MessageChannel) context.getBean("sourceChannel"); + sourceChannel.send(new GenericMessage("123", "test")); + MessageChannel targetChannel = (MessageChannel) context.getBean("targetChannel"); + MessageBus bus = (MessageBus) context.getBean("bus"); + ConsumerPolicy policy = new ConsumerPolicy(); + Subscription subscription = new Subscription(); + subscription.setChannel("sourceChannel"); + subscription.setEndpoint("endpoint"); + subscription.setPolicy(policy); + bus.activateSubscription(subscription); + Message result = targetChannel.receive(100); + assertEquals("test", result.getPayload()); + } + + @Test + public void testExactlyOneEndpointReceivesUnicastMessage() { + PointToPointChannel inputChannel = new PointToPointChannel(); + PointToPointChannel outputChannel1 = new PointToPointChannel(); + PointToPointChannel outputChannel2 = new PointToPointChannel(); + GenericMessageEndpoint endpoint1 = new GenericMessageEndpoint(); + endpoint1.setDefaultOutputChannelName("output1"); + endpoint1.setInputChannelName("input"); + GenericMessageEndpoint endpoint2 = new GenericMessageEndpoint(); + endpoint2.setDefaultOutputChannelName("output2"); + endpoint2.setInputChannelName("input"); + MessageBus bus = new MessageBus(); + bus.registerChannel("input", inputChannel); + bus.registerChannel("output1", outputChannel1); + bus.registerChannel("output2", outputChannel2); + bus.registerEndpoint("endpoint1", endpoint1); + bus.registerEndpoint("endpoint2", endpoint2); + bus.start(); + inputChannel.send(new StringMessage(1, "testing")); + Message message1 = outputChannel1.receive(100); + Message message2 = outputChannel2.receive(0); + bus.stop(); + assertTrue("exactly one message should be null", message1 == null ^ message2 == null); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/bus/UnicastMessageDispatcherTests.java b/spring-integration-core/src/test/java/org/springframework/integration/bus/UnicastMessageDispatcherTests.java new file mode 100644 index 0000000000..1a3d19ca05 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/bus/UnicastMessageDispatcherTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.bus; + +import static org.junit.Assert.assertTrue; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Test; + +import org.springframework.integration.channel.PointToPointChannel; +import org.springframework.integration.endpoint.GenericMessageEndpoint; +import org.springframework.integration.endpoint.MessageEndpoint; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.StringMessage; + +/** + * @author Mark Fisher + */ +public class UnicastMessageDispatcherTests { + + @Test + public void testDispatcherSendsToExactlyOneEndpoint() throws InterruptedException { + final AtomicBoolean endpoint1Received = new AtomicBoolean(); + final AtomicBoolean endpoint2Received = new AtomicBoolean(); + final CountDownLatch latch = new CountDownLatch(1); + MessageEndpoint endpoint1 = new GenericMessageEndpoint() { + @Override + public void messageReceived(Message message) { + endpoint1Received.set(true); + latch.countDown(); + } + }; + MessageEndpoint endpoint2 = new GenericMessageEndpoint() { + @Override + public void messageReceived(Message message) { + endpoint2Received.set(true); + latch.countDown(); + } + }; + ConsumerPolicy policy = new ConsumerPolicy(); + PointToPointChannel channel = new PointToPointChannel(); + channel.send(new StringMessage(1, "test")); + MessageRetriever retriever = new ChannelPollingMessageRetriever(channel, policy); + UnicastMessageDispatcher dispatcher = new UnicastMessageDispatcher(retriever, policy); + dispatcher.addExecutor(new MessageReceivingExecutor(endpoint1, 1, 1)); + dispatcher.addExecutor(new MessageReceivingExecutor(endpoint2, 1, 1)); + dispatcher.dispatch(); + latch.await(100, TimeUnit.MILLISECONDS); + assertTrue("exactly one endpoint should have received message", + endpoint1Received.get() ^ endpoint2Received.get()); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/bus/messageBusTests.xml b/spring-integration-core/src/test/java/org/springframework/integration/bus/messageBusTests.xml new file mode 100644 index 0000000000..ca1249bab7 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/bus/messageBusTests.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/channel/PointToPointChannelTests.java b/spring-integration-core/src/test/java/org/springframework/integration/channel/PointToPointChannelTests.java new file mode 100644 index 0000000000..0ed295f04b --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/channel/PointToPointChannelTests.java @@ -0,0 +1,262 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.channel; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessageSelector; +import org.springframework.integration.message.GenericMessage; + +/** + * @author Mark Fisher + */ +public class PointToPointChannelTests { + + @Test + public void testSimpleSendAndReceive() throws Exception { + final AtomicBoolean messageReceived = new AtomicBoolean(false); + final CountDownLatch latch = new CountDownLatch(1); + final PointToPointChannel channel = new PointToPointChannel(); + new Thread(new Runnable() { + public void run() { + Message message = channel.receive(); + if (message != null) { + messageReceived.set(true); + latch.countDown(); + } + } + }).start(); + assertFalse(messageReceived.get()); + channel.send(new GenericMessage(1, "testing")); + latch.await(25, TimeUnit.MILLISECONDS); + assertTrue(messageReceived.get()); + } + + @Test + public void testImmediateReceive() throws Exception { + final AtomicBoolean messageReceived = new AtomicBoolean(false); + final PointToPointChannel channel = new PointToPointChannel(); + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + Executor singleThreadExecutor = Executors.newSingleThreadExecutor(); + Runnable receiveTask1 = new Runnable() { + public void run() { + Message message = channel.receive(0); + if (message != null) { + messageReceived.set(true); + } + latch1.countDown(); + } + }; + Runnable sendTask = new Runnable() { + public void run() { + channel.send(new GenericMessage(1, "testing")); + } + }; + singleThreadExecutor.execute(receiveTask1); + latch1.await(); + singleThreadExecutor.execute(sendTask); + assertFalse(messageReceived.get()); + Runnable receiveTask2 = new Runnable() { + public void run() { + Message message = channel.receive(0); + if (message != null) { + messageReceived.set(true); + } + latch2.countDown(); + } + }; + singleThreadExecutor.execute(receiveTask2); + latch2.await(); + assertTrue(messageReceived.get()); + } + + @Test + public void testBlockingReceiveWithNoTimeout() throws Exception{ + final PointToPointChannel channel = new PointToPointChannel(); + final AtomicBoolean receiveInterrupted = new AtomicBoolean(false); + final CountDownLatch latch = new CountDownLatch(1); + Thread t = new Thread(new Runnable() { + public void run() { + Message message = channel.receive(); + receiveInterrupted.set(true); + assertTrue(message == null); + latch.countDown(); + } + }); + t.start(); + assertFalse(receiveInterrupted.get()); + t.interrupt(); + latch.await(); + assertTrue(receiveInterrupted.get()); + } + + @Test + public void testBlockingReceiveWithTimeout() throws Exception{ + final PointToPointChannel channel = new PointToPointChannel(); + final AtomicBoolean receiveInterrupted = new AtomicBoolean(false); + final CountDownLatch latch = new CountDownLatch(1); + Thread t = new Thread(new Runnable() { + public void run() { + Message message = channel.receive(10000); + receiveInterrupted.set(true); + assertTrue(message == null); + latch.countDown(); + } + }); + t.start(); + assertFalse(receiveInterrupted.get()); + t.interrupt(); + latch.await(); + assertTrue(receiveInterrupted.get()); + } + + @Test + public void testImmediateSend() { + PointToPointChannel channel = new PointToPointChannel(3); + boolean result1 = channel.send(new GenericMessage(1, "test-1")); + assertTrue(result1); + boolean result2 = channel.send(new GenericMessage(2, "test-2"), 100); + assertTrue(result2); + boolean result3 = channel.send(new GenericMessage(3, "test-3"), 0); + assertTrue(result3); + boolean result4 = channel.send(new GenericMessage(4, "test-4"), 0); + assertFalse(result4); + } + + @Test + public void testBlockingSendWithNoTimeout() throws Exception{ + final PointToPointChannel channel = new PointToPointChannel(1); + boolean result1 = channel.send(new GenericMessage(1, "test-1")); + assertTrue(result1); + final AtomicBoolean sendInterrupted = new AtomicBoolean(false); + final CountDownLatch latch = new CountDownLatch(1); + Thread t = new Thread(new Runnable() { + public void run() { + channel.send(new GenericMessage(2, "test-2")); + sendInterrupted.set(true); + latch.countDown(); + } + }); + t.start(); + assertFalse(sendInterrupted.get()); + t.interrupt(); + latch.await(); + assertTrue(sendInterrupted.get()); + } + + @Test + public void testBlockingSendWithTimeout() throws Exception{ + final PointToPointChannel channel = new PointToPointChannel(1); + boolean result1 = channel.send(new GenericMessage(1, "test-1")); + assertTrue(result1); + final AtomicBoolean sendInterrupted = new AtomicBoolean(false); + final CountDownLatch latch = new CountDownLatch(1); + Thread t = new Thread(new Runnable() { + public void run() { + channel.send(new GenericMessage(2, "test-2"), 10000); + sendInterrupted.set(true); + latch.countDown(); + } + }); + t.start(); + assertFalse(sendInterrupted.get()); + t.interrupt(); + latch.await(); + assertTrue(sendInterrupted.get()); + } + + @Test + public void testSelectorMatchesWithinTimeout() throws Exception { + final PointToPointChannel channel = new PointToPointChannel(); + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference> messageRef = new AtomicReference>(); + Thread receiver = new Thread(new Runnable() { + public void run() { + Message message = channel.receive(new MessageSelector() { + public boolean accept(Message message) { + return (((Integer)message.getId()).intValue() == 3); + } + }, 100); + messageRef.set(message); + latch.countDown(); + } + }); + receiver.start(); + Thread sender = new Thread(new Runnable() { + public void run() { + channel.send(new GenericMessage(1, "test-1")); + try { Thread.sleep(5); } catch (Exception e) {} + channel.send(new GenericMessage(2, "test-2")); + try { Thread.sleep(5); } catch (Exception e) {} + channel.send(new GenericMessage(3, "test-3")); + try { Thread.sleep(100); } catch (Exception e) {} + channel.send(new GenericMessage(4, "test-4")); + } + }); + sender.start(); + latch.await(); + assertNotNull("message reference should not be null", messageRef.get()); + String payload = (String) messageRef.get().getPayload(); + assertEquals("expected 'test-3', but message was '" + payload, "test-3", payload); + } + + @Test + public void testSelectorDoesNotMatchWithinTimeout() throws Exception { + final PointToPointChannel channel = new PointToPointChannel(); + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference> messageRef = new AtomicReference>(); + Thread receiver = new Thread(new Runnable() { + public void run() { + Message message = channel.receive(new MessageSelector() { + public boolean accept(Message message) { + return (((Integer)message.getId()).intValue() == 3); + } + }, 7); + messageRef.set(message); + latch.countDown(); + } + }); + receiver.start(); + Thread sender = new Thread(new Runnable() { + public void run() { + channel.send(new GenericMessage(1, "test-1")); + try { Thread.sleep(5); } catch (Exception e) {} + channel.send(new GenericMessage(2, "test-2")); + try { Thread.sleep(5); } catch (Exception e) {} + channel.send(new GenericMessage(3, "test-3")); + } + }); + sender.start(); + latch.await(); + assertNull(messageRef.get()); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/ChannelParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/ChannelParserTests.java new file mode 100644 index 0000000000..93235acba8 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/ChannelParserTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.config; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.message.GenericMessage; + +/** + * @author Mark Fisher + */ +public class ChannelParserTests { + + @Test + public void testSimpleChannelWithDefaults() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( + "channelParserTests.xml", this.getClass()); + MessageChannel channel = (MessageChannel) context.getBean("testChannel"); + for (int i = 0; i < 10; i++) { + boolean result = channel.send(new GenericMessage(1, "test"), 10); + assertTrue(result); + } + assertFalse(channel.send(new GenericMessage(1, "test"), 3)); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/EndpointParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/EndpointParserTests.java new file mode 100644 index 0000000000..99c6eb1a06 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/EndpointParserTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.config; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.message.GenericMessage; + +/** + * @author Mark Fisher + */ +public class EndpointParserTests { + + @Test + public void testSimpleEndpoint() throws InterruptedException { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( + "genericEndpointTests.xml", this.getClass()); + context.start(); + MessageChannel channel = (MessageChannel) context.getBean("testChannel"); + TestHandler handler = (TestHandler) context.getBean("testHandler"); + assertNull(handler.getMessageString()); + channel.send(new GenericMessage(1, "test")); + handler.getLatch().await(50, TimeUnit.MILLISECONDS); + assertEquals("test", handler.getMessageString()); + } + + @Test + public void testHandlerAdapterEndpoint() throws InterruptedException { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( + "handlerAdapterEndpointTests.xml", this.getClass()); + context.start(); + MessageChannel channel = (MessageChannel) context.getBean("testChannel"); + TestBean bean = (TestBean) context.getBean("testBean"); + assertNull(bean.getMessage()); + channel.send(new GenericMessage(1, "test")); + bean.getLatch().await(500, TimeUnit.MILLISECONDS); + assertEquals("test", bean.getMessage()); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/TestBean.java b/spring-integration-core/src/test/java/org/springframework/integration/config/TestBean.java new file mode 100644 index 0000000000..24e5eaf84b --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/TestBean.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.config; + +import java.util.concurrent.CountDownLatch; + +/** + * @author Mark Fisher + */ +public class TestBean { + + private String message; + + private CountDownLatch latch; + + + public TestBean(int countdown) { + this.latch = new CountDownLatch(countdown); + } + + public CountDownLatch getLatch() { + return this.latch; + } + + public void store(String message) { + this.message = message; + latch.countDown(); + } + + public String getMessage() { + return this.message; + } +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/TestHandler.java b/spring-integration-core/src/test/java/org/springframework/integration/config/TestHandler.java new file mode 100644 index 0000000000..8c4c6274c2 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/TestHandler.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.config; + +import java.util.concurrent.CountDownLatch; + +import org.springframework.integration.handler.MessageHandler; +import org.springframework.integration.message.Message; + +/** + * @author Mark Fisher + */ +public class TestHandler implements MessageHandler { + + private String messageString; + + private CountDownLatch latch; + + + public TestHandler(int countdown) { + this.latch = new CountDownLatch(countdown); + } + + public Message handle(Message message) { + this.messageString = (String) message.getPayload(); + this.latch.countDown(); + return null; + } + + public String getMessageString() { + return this.messageString; + } + + public CountDownLatch getLatch() { + return this.latch; + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/channelParserTests.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/channelParserTests.xml new file mode 100644 index 0000000000..3e14d73a70 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/channelParserTests.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/genericEndpointTests.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/genericEndpointTests.xml new file mode 100644 index 0000000000..195242c1ab --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/genericEndpointTests.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/handlerAdapterEndpointTests.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/handlerAdapterEndpointTests.xml new file mode 100644 index 0000000000..e8497b9d32 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/handlerAdapterEndpointTests.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/endpoint/GenericMessageEndpointTests.java b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/GenericMessageEndpointTests.java new file mode 100644 index 0000000000..29ee7f5166 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/GenericMessageEndpointTests.java @@ -0,0 +1,85 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.endpoint; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; +import org.springframework.integration.bus.MessageBus; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.channel.PointToPointChannel; +import org.springframework.integration.handler.MessageHandler; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.StringMessage; + +/** + * @author Mark Fisher + */ +public class GenericMessageEndpointTests { + + @Test + public void testDefaultReplyChannel() throws Exception { + MessageChannel channel = new PointToPointChannel(); + MessageChannel replyChannel = new PointToPointChannel(); + MessageHandler handler = new MessageHandler() { + public Message handle(Message message) { + return new StringMessage("123", "hello " + message.getPayload()); + } + }; + GenericMessageEndpoint endpoint = new GenericMessageEndpoint(); + endpoint.setInputChannelName("testChannel"); + endpoint.setHandler(handler); + endpoint.setDefaultOutputChannelName("replyChannel"); + MessageBus bus = new MessageBus(); + bus.registerChannel("testChannel", channel); + bus.registerEndpoint("testEndpoint", endpoint); + bus.registerChannel("replyChannel", replyChannel); + bus.start(); + StringMessage testMessage = new StringMessage(1, "test"); + channel.send(testMessage); + Message reply = replyChannel.receive(50); + assertNotNull(reply); + assertEquals("hello test", reply.getPayload()); + } + + @Test + public void testExplicitReplyChannel() throws Exception { + MessageChannel channel = new PointToPointChannel(); + final MessageChannel replyChannel = new PointToPointChannel(); + MessageHandler handler = new MessageHandler() { + public Message handle(Message message) { + return new StringMessage("123", "hello " + message.getPayload()); + } + }; + GenericMessageEndpoint endpoint = new GenericMessageEndpoint(); + endpoint.setInputChannelName("testChannel"); + endpoint.setHandler(handler); + MessageBus bus = new MessageBus(); + bus.registerChannel("testChannel", channel); + bus.registerEndpoint("testEndpoint", endpoint); + bus.registerChannel("replyChannel", replyChannel); + bus.start(); + StringMessage testMessage = new StringMessage(1, "test"); + testMessage.getHeader().setReplyChannelName("replyChannel"); + channel.send(testMessage); + Message reply = replyChannel.receive(50); + assertNotNull(reply); + assertEquals("hello test", reply.getPayload()); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/endpoint/annotation/InboundChannelAdapterTestBean.java b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/annotation/InboundChannelAdapterTestBean.java new file mode 100644 index 0000000000..6552b4a7aa --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/annotation/InboundChannelAdapterTestBean.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.endpoint.annotation; + +import org.springframework.integration.annotation.Handler; +import org.springframework.integration.annotation.MessageEndpoint; +import org.springframework.integration.annotation.Polled; + +/** + * @author Mark Fisher + */ +@MessageEndpoint(defaultOutput="outputChannel") +public class InboundChannelAdapterTestBean { + + @Polled(period=100) + public String getName() { + return "world"; + } + + @Handler + public String sayHello(String name) { + return "hello " + name; + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/endpoint/annotation/MessageEndpointAnnotationPostProcessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/annotation/MessageEndpointAnnotationPostProcessorTests.java new file mode 100644 index 0000000000..00a9fda046 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/annotation/MessageEndpointAnnotationPostProcessorTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.endpoint.annotation; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.GenericMessage; + +/** + * @author Mark Fisher + */ +public class MessageEndpointAnnotationPostProcessorTests { + + @Test + public void testSimpleHandler() throws InterruptedException { + AbstractApplicationContext context = new ClassPathXmlApplicationContext("simpleAnnotatedEndpointTests.xml", this.getClass()); + context.start(); + MessageChannel inputChannel = (MessageChannel) context.getBean("inputChannel"); + MessageChannel outputChannel = (MessageChannel) context.getBean("outputChannel"); + inputChannel.send(new GenericMessage(1, "world")); + Message message = outputChannel.receive(1000); + assertEquals("hello world", message.getPayload()); + context.stop(); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/endpoint/annotation/OutboundChannelAdapterTestBean.java b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/annotation/OutboundChannelAdapterTestBean.java new file mode 100644 index 0000000000..8bcd6becd4 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/annotation/OutboundChannelAdapterTestBean.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.endpoint.annotation; + +import org.springframework.integration.annotation.DefaultOutput; +import org.springframework.integration.annotation.Handler; +import org.springframework.integration.annotation.MessageEndpoint; + +/** + * @author Mark Fisher + */ +@MessageEndpoint(input="inputChannel") +public class OutboundChannelAdapterTestBean { + + @Handler + public String sayHello(String name) { + return "hello " + name; + } + + @DefaultOutput + public void sendGreeting(String greeting) { + System.out.println(greeting); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/endpoint/annotation/SimpleAnnotatedEndpoint.java b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/annotation/SimpleAnnotatedEndpoint.java new file mode 100644 index 0000000000..67691f85a3 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/annotation/SimpleAnnotatedEndpoint.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.endpoint.annotation; + +import org.springframework.integration.annotation.Handler; +import org.springframework.integration.annotation.MessageEndpoint; + +/** + * @author Mark Fisher + */ +@MessageEndpoint(input="inputChannel", defaultOutput="outputChannel", pollPeriod=10) +public class SimpleAnnotatedEndpoint { + + @Handler + public String sayHello(String name) { + return "hello " + name; + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/endpoint/annotation/simpleAnnotatedEndpointTests.xml b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/annotation/simpleAnnotatedEndpointTests.xml new file mode 100644 index 0000000000..783e2683e3 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/annotation/simpleAnnotatedEndpointTests.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/endpoint/annotation/simpleAnnotatedEndpointWithAutoCreateChannelTests.xml b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/annotation/simpleAnnotatedEndpointWithAutoCreateChannelTests.xml new file mode 100644 index 0000000000..f02e95b678 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/annotation/simpleAnnotatedEndpointWithAutoCreateChannelTests.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/handler/MessageHandlerChainTests.java b/spring-integration-core/src/test/java/org/springframework/integration/handler/MessageHandlerChainTests.java new file mode 100644 index 0000000000..e1d80a4903 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/handler/MessageHandlerChainTests.java @@ -0,0 +1,86 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.handler; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import org.springframework.integration.message.Message; +import org.springframework.integration.message.StringMessage; + +/** + * @author Mark Fisher + */ +public class MessageHandlerChainTests { + + @Test + public void testSimpleChain() { + MessageHandlerChain chain = new MessageHandlerChain(); + chain.add(new TestHandler("a")); + chain.add(new TestHandler("b")); + chain.add(new TestHandler("c")); + chain.add(new TestHandler("d")); + Message result = chain.handle(new StringMessage(1, "!")); + assertEquals("!abcd", result.getPayload()); + } + + @Test + public void testChainWithInterceptors() { + MessageHandler handler1 = new TestHandler("*"); + MessageHandler handler2 = new TestInterceptingHandler("2", handler1); + MessageHandler handler3 = new TestInterceptingHandler("3", handler2); + MessageHandler handler4 = new TestInterceptingHandler("4", handler3); + MessageHandlerChain chain = new MessageHandlerChain(); + chain.add(new TestHandler("a")); + chain.add(handler4); + chain.add(new TestHandler("b")); + Message result = chain.handle(new StringMessage(1, "!")); + assertEquals("234!a*234b", result.getPayload()); + } + + + private static class TestHandler implements MessageHandler { + + private String text; + + TestHandler(String text) { + this.text = text; + } + + public Message handle(Message message) { + return new StringMessage(1, message.getPayload() + text); + } + } + + + private static class TestInterceptingHandler extends InterceptingMessageHandler { + + private String text; + + TestInterceptingHandler(String text, MessageHandler target) { + super(target); + this.text = text; + } + + public Message handle(Message message, MessageHandler target) { + message = target.handle(new StringMessage(1, text + message.getPayload())); + return new StringMessage(1, message.getPayload() + text); + } + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/MultiChannelRouterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/MultiChannelRouterTests.java new file mode 100644 index 0000000000..591644f769 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/MultiChannelRouterTests.java @@ -0,0 +1,170 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.router; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; +import org.springframework.integration.MessageHandlingException; +import org.springframework.integration.MessagingConfigurationException; +import org.springframework.integration.channel.ChannelRegistry; +import org.springframework.integration.channel.DefaultChannelRegistry; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.channel.PointToPointChannel; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.StringMessage; + +/** + * @author Mark Fisher + */ +public class MultiChannelRouterTests { + + @Test + public void testRoutingWithChannelResolver() { + final PointToPointChannel channel1 = new PointToPointChannel(); + final PointToPointChannel channel2 = new PointToPointChannel(); + MultiChannelResolver channelResolver = new MultiChannelResolver() { + public List resolve(Message message) { + List channels = new ArrayList(); + channels.add(channel1); + channels.add(channel2); + return channels; + } + }; + MultiChannelRouter router = new MultiChannelRouter(); + router.setChannelResolver(channelResolver); + router.afterPropertiesSet(); + Message message = new StringMessage("123", "test"); + router.handle(message); + Message result1 = channel1.receive(25); + assertNotNull(result1); + assertEquals("test", result1.getPayload()); + Message result2 = channel2.receive(25); + assertNotNull(result2); + assertEquals("test", result2.getPayload()); + } + + @Test + public void testRoutingWithChannelNameResolver() { + MultiChannelNameResolver channelNameResolver = new MultiChannelNameResolver() { + public String[] resolve(Message message) { + return new String[] {"channel1", "channel2"}; + } + }; + PointToPointChannel channel1 = new PointToPointChannel(); + PointToPointChannel channel2 = new PointToPointChannel(); + ChannelRegistry channelRegistry = new DefaultChannelRegistry(); + channelRegistry.registerChannel("channel1", channel1); + channelRegistry.registerChannel("channel2", channel2); + MultiChannelRouter router = new MultiChannelRouter(); + router.setChannelNameResolver(channelNameResolver); + router.setChannelRegistry(channelRegistry); + router.afterPropertiesSet(); + Message message = new StringMessage("123", "test"); + router.handle(message); + Message result1 = channel1.receive(25); + assertNotNull(result1); + assertEquals("test", result1.getPayload()); + Message result2 = channel2.receive(25); + assertNotNull(result2); + assertEquals("test", result2.getPayload()); + } + + @Test(expected=MessagingConfigurationException.class) + public void testConfiguringBothChannelResolverAndChannelNameResolverIsNotAllowed() { + MultiChannelResolver channelResolver = new MultiChannelResolver() { + public List resolve(Message message) { + return null; + } + }; + MultiChannelNameResolver channelNameResolver = new MultiChannelNameResolver() { + public String[] resolve(Message message) { + return null; + } + }; + MultiChannelRouter router = new MultiChannelRouter(); + router.setChannelResolver(channelResolver); + router.setChannelNameResolver(channelNameResolver); + router.afterPropertiesSet(); + } + + public void testChannelResolutionFailureIgnoredByDefault() { + MultiChannelResolver channelResolver = new MultiChannelResolver() { + public List resolve(Message message) { + return null; + } + }; + MultiChannelRouter router = new MultiChannelRouter(); + router.setChannelResolver(channelResolver); + router.setResolutionRequired(true); + router.afterPropertiesSet(); + Message message = new StringMessage("123", "test"); + router.handle(message); + } + + @Test(expected=MessageHandlingException.class) + public void testChannelResolutionFailureThrowsExceptionWhenResolutionRequired() { + MultiChannelResolver channelResolver = new MultiChannelResolver() { + public List resolve(Message message) { + return null; + } + }; + MultiChannelRouter router = new MultiChannelRouter(); + router.setChannelResolver(channelResolver); + router.setResolutionRequired(true); + router.afterPropertiesSet(); + Message message = new StringMessage("123", "test"); + router.handle(message); + } + + public void testChannelNameResolutionFailureIgnoredByDefault() { + MultiChannelNameResolver channelNameResolver = new MultiChannelNameResolver() { + public String[] resolve(Message message) { + return new String[] {"noSuchChannel"}; + } + }; + ChannelRegistry channelRegistry = new DefaultChannelRegistry(); + MultiChannelRouter router = new MultiChannelRouter(); + router.setChannelNameResolver(channelNameResolver); + router.setChannelRegistry(channelRegistry); + router.afterPropertiesSet(); + Message message = new StringMessage("123", "test"); + router.handle(message); + } + + @Test(expected=MessageHandlingException.class) + public void testChannelNameResolutionFailureThrowsExceptionWhenResolutionRequired() { + MultiChannelNameResolver channelNameResolver = new MultiChannelNameResolver() { + public String[] resolve(Message message) { + return new String[] {"noSuchChannel"}; + } + }; + ChannelRegistry channelRegistry = new DefaultChannelRegistry(); + MultiChannelRouter router = new MultiChannelRouter(); + router.setChannelNameResolver(channelNameResolver); + router.setChannelRegistry(channelRegistry); + router.setResolutionRequired(true); + router.afterPropertiesSet(); + Message message = new StringMessage("123", "test"); + router.handle(message); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/RecipientListRouterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/RecipientListRouterTests.java new file mode 100644 index 0000000000..e4d2745350 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/RecipientListRouterTests.java @@ -0,0 +1,119 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.router; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +import org.springframework.integration.MessagingConfigurationException; +import org.springframework.integration.channel.ChannelRegistry; +import org.springframework.integration.channel.DefaultChannelRegistry; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.channel.PointToPointChannel; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.StringMessage; + +/** + * @author Mark Fisher + */ +public class RecipientListRouterTests { + + @Test + public void testRoutingWithChannelList() { + PointToPointChannel channel1 = new PointToPointChannel(); + PointToPointChannel channel2 = new PointToPointChannel(); + List channels = new ArrayList(); + channels.add(channel1); + channels.add(channel2); + RecipientListRouter router = new RecipientListRouter(); + router.setChannels(channels); + router.afterPropertiesSet(); + Message message = new StringMessage("123", "test"); + router.handle(message); + Message result1 = channel1.receive(25); + assertNotNull(result1); + assertEquals("test", result1.getPayload()); + Message result2 = channel2.receive(25); + assertNotNull(result2); + assertEquals("test", result2.getPayload()); + } + + @Test + public void testRoutingWithChannelNames() { + PointToPointChannel channel1 = new PointToPointChannel(); + PointToPointChannel channel2 = new PointToPointChannel(); + ChannelRegistry channelRegistry = new DefaultChannelRegistry(); + channelRegistry.registerChannel("channel1", channel1); + channelRegistry.registerChannel("channel2", channel2); + RecipientListRouter router = new RecipientListRouter(); + router.setChannelNames(new String[] {"channel1", "channel2"}); + router.setChannelRegistry(channelRegistry); + router.afterPropertiesSet(); + Message message = new StringMessage("123", "test"); + router.handle(message); + Message result1 = channel1.receive(25); + assertNotNull(result1); + assertEquals("test", result1.getPayload()); + Message result2 = channel2.receive(25); + assertNotNull(result2); + assertEquals("test", result2.getPayload()); + } + + @Test + public void testRoutingToSingleChannelByName() { + PointToPointChannel channel1 = new PointToPointChannel(); + PointToPointChannel channel2 = new PointToPointChannel(); + ChannelRegistry channelRegistry = new DefaultChannelRegistry(); + channelRegistry.registerChannel("channel1", channel1); + channelRegistry.registerChannel("channel2", channel2); + RecipientListRouter router = new RecipientListRouter(); + router.setChannelNames(new String[] {"channel1"}); + router.setChannelRegistry(channelRegistry); + router.afterPropertiesSet(); + Message message = new StringMessage("123", "test"); + router.handle(message); + Message result1 = channel1.receive(25); + assertNotNull(result1); + assertEquals("test", result1.getPayload()); + Message result2 = channel2.receive(5); + assertNull(result2); + } + + @Test(expected=MessagingConfigurationException.class) + public void testConfigurationExceptionWhenBothChannelsAndNamesAreProvided() { + PointToPointChannel channel1 = new PointToPointChannel(); + PointToPointChannel channel2 = new PointToPointChannel(); + List channels = new ArrayList(); + channels.add(channel1); + channels.add(channel2); + ChannelRegistry channelRegistry = new DefaultChannelRegistry(); + channelRegistry.registerChannel("channel1", channel1); + channelRegistry.registerChannel("channel2", channel2); + RecipientListRouter router = new RecipientListRouter(); + router.setChannels(channels); + router.setChannelNames(new String[] {"channel1"}); + router.setChannelRegistry(channelRegistry); + router.afterPropertiesSet(); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/RouterMessageHandlerAdapterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/RouterMessageHandlerAdapterTests.java new file mode 100644 index 0000000000..b692ac30ca --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/RouterMessageHandlerAdapterTests.java @@ -0,0 +1,132 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.router; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.lang.reflect.Method; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.junit.Test; + +import org.springframework.integration.MessagingConfigurationException; +import org.springframework.integration.channel.ChannelRegistry; +import org.springframework.integration.channel.DefaultChannelRegistry; +import org.springframework.integration.channel.PointToPointChannel; +import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.message.Message; + +/** + * @author Mark Fisher + */ +public class RouterMessageHandlerAdapterTests { + + @Test + public void testChannelNameResolutionByPayload() throws Exception { + RoutingTestBean testBean = new RoutingTestBean(); + Method fooMethod = testBean.getClass().getMethod("foo", String.class); + Map attribs = new ConcurrentHashMap(); + RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, fooMethod, attribs); + Message message = new GenericMessage("123", "bar"); + PointToPointChannel barChannel = new PointToPointChannel(); + ChannelRegistry channelRegistry = new DefaultChannelRegistry(); + channelRegistry.registerChannel("bar-channel", barChannel); + adapter.setChannelRegistry(channelRegistry); + adapter.afterPropertiesSet(); + adapter.handle(message); + Message result = barChannel.receive(0); + assertNotNull(result); + assertEquals("bar", result.getPayload()); + } + + @Test + public void testChannelNameResolutionByProperty() throws Exception { + RoutingTestBean testBean = new RoutingTestBean(); + Method fooMethod = testBean.getClass().getMethod("foo", String.class); + Map attribs = new ConcurrentHashMap(); + attribs.put("property", "returnAddress"); + RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, fooMethod, attribs); + Message message = new GenericMessage("123", "bar"); + message.getHeader().setProperty("returnAddress", "baz"); + PointToPointChannel barChannel = new PointToPointChannel(); + PointToPointChannel bazChannel = new PointToPointChannel(); + ChannelRegistry channelRegistry = new DefaultChannelRegistry(); + channelRegistry.registerChannel("bar-channel", barChannel); + channelRegistry.registerChannel("baz-channel", bazChannel); + adapter.setChannelRegistry(channelRegistry); + adapter.afterPropertiesSet(); + adapter.handle(message); + Message message1 = barChannel.receive(0); + assertNull(message1); + Message message2 = bazChannel.receive(0); + assertNotNull(message2); + assertEquals("bar", message2.getPayload()); + } + + @Test + public void testChannelNameResolutionByAttribute() throws Exception { + RoutingTestBean testBean = new RoutingTestBean(); + Method fooMethod = testBean.getClass().getMethod("foo", String.class); + Map attribs = new ConcurrentHashMap(); + attribs.put("attribute", "returnAddress"); + RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, fooMethod, attribs); + Message message = new GenericMessage("123", "bar"); + message.getHeader().setProperty("returnAddress", "bad"); + message.getHeader().setAttribute("returnAddress", "baz"); + PointToPointChannel barChannel = new PointToPointChannel(); + PointToPointChannel badChannel = new PointToPointChannel(); + PointToPointChannel bazChannel = new PointToPointChannel(); + ChannelRegistry channelRegistry = new DefaultChannelRegistry(); + channelRegistry.registerChannel("bar-channel", barChannel); + channelRegistry.registerChannel("bad-channel", badChannel); + channelRegistry.registerChannel("baz-channel", bazChannel); + adapter.setChannelRegistry(channelRegistry); + adapter.afterPropertiesSet(); + adapter.handle(message); + Message message1 = barChannel.receive(0); + assertNull(message1); + Message message2 = badChannel.receive(0); + assertNull(message2); + Message message3 = bazChannel.receive(0); + assertNotNull(message3); + assertEquals("bar", message3.getPayload()); + } + + @Test(expected=MessagingConfigurationException.class) + public void testFailsWhenPropertyAndAttributeAreBothProvided() throws Exception { + RoutingTestBean testBean = new RoutingTestBean(); + Method fooMethod = testBean.getClass().getMethod("foo", String.class); + Map attribs = new ConcurrentHashMap(); + attribs.put("property", "targetChannel"); + attribs.put("attribute", "returnAddress"); + RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, fooMethod, attribs); + adapter.afterPropertiesSet(); + adapter.handle(new GenericMessage("123", "testing")); + } + + + public static class RoutingTestBean { + + public String foo(String name) { + return name + "-channel"; + } + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/SingleChannelRouterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/SingleChannelRouterTests.java new file mode 100644 index 0000000000..3f58204976 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/SingleChannelRouterTests.java @@ -0,0 +1,156 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.router; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; + +import org.springframework.integration.MessageHandlingException; +import org.springframework.integration.MessagingConfigurationException; +import org.springframework.integration.channel.ChannelRegistry; +import org.springframework.integration.channel.DefaultChannelRegistry; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.channel.PointToPointChannel; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.StringMessage; + +/** + * @author Mark Fisher + */ +public class SingleChannelRouterTests { + + @Test + public void testRoutingWithChannelResolver() { + final PointToPointChannel channel = new PointToPointChannel(); + ChannelResolver channelResolver = new ChannelResolver() { + public MessageChannel resolve(Message message) { + return channel; + } + }; + SingleChannelRouter router = new SingleChannelRouter(); + router.setChannelResolver(channelResolver); + router.afterPropertiesSet(); + Message message = new StringMessage("123", "test"); + router.handle(message); + Message result = channel.receive(25); + assertNotNull(result); + assertEquals("test", result.getPayload()); + } + + @Test + public void testRoutingWithChannelNameResolver() { + ChannelNameResolver channelNameResolver = new ChannelNameResolver() { + public String resolve(Message message) { + return "testChannel"; + } + }; + PointToPointChannel channel = new PointToPointChannel(); + ChannelRegistry channelRegistry = new DefaultChannelRegistry(); + channelRegistry.registerChannel("testChannel", channel); + SingleChannelRouter router = new SingleChannelRouter(); + router.setChannelNameResolver(channelNameResolver); + router.setChannelRegistry(channelRegistry); + router.afterPropertiesSet(); + Message message = new StringMessage("123", "test"); + router.handle(message); + Message result = channel.receive(25); + assertNotNull(result); + assertEquals("test", result.getPayload()); + } + + @Test(expected=MessagingConfigurationException.class) + public void testConfiguringBothChannelResolverAndChannelNameResolverIsNotAllowed() { + ChannelResolver channelResolver = new ChannelResolver() { + public MessageChannel resolve(Message message) { + return new PointToPointChannel(); + } + }; + ChannelNameResolver channelNameResolver = new ChannelNameResolver() { + public String resolve(Message message) { + return ""; + } + }; + SingleChannelRouter router = new SingleChannelRouter(); + router.setChannelResolver(channelResolver); + router.setChannelNameResolver(channelNameResolver); + router.afterPropertiesSet(); + } + + public void testChannelResolutionFailureIgnoredByDefault() { + ChannelResolver channelResolver = new ChannelResolver() { + public MessageChannel resolve(Message message) { + return null; + } + }; + SingleChannelRouter router = new SingleChannelRouter(); + router.setChannelResolver(channelResolver); + router.setResolutionRequired(true); + router.afterPropertiesSet(); + Message message = new StringMessage("123", "test"); + router.handle(message); + } + + @Test(expected=MessageHandlingException.class) + public void testChannelResolutionFailureThrowsExceptionWhenResolutionRequired() { + ChannelResolver channelResolver = new ChannelResolver() { + public MessageChannel resolve(Message message) { + return null; + } + }; + SingleChannelRouter router = new SingleChannelRouter(); + router.setChannelResolver(channelResolver); + router.setResolutionRequired(true); + router.afterPropertiesSet(); + Message message = new StringMessage("123", "test"); + router.handle(message); + } + + public void testChannelNameResolutionFailureIgnoredByDefault() { + ChannelNameResolver channelNameResolver = new ChannelNameResolver() { + public String resolve(Message message) { + return "noSuchChannel"; + } + }; + ChannelRegistry channelRegistry = new DefaultChannelRegistry(); + SingleChannelRouter router = new SingleChannelRouter(); + router.setChannelNameResolver(channelNameResolver); + router.setChannelRegistry(channelRegistry); + router.afterPropertiesSet(); + Message message = new StringMessage("123", "test"); + router.handle(message); + } + + @Test(expected=MessageHandlingException.class) + public void testChannelNameResolutionFailureThrowsExceptionWhenResolutionRequired() { + ChannelNameResolver channelNameResolver = new ChannelNameResolver() { + public String resolve(Message message) { + return "noSuchChannel"; + } + }; + ChannelRegistry channelRegistry = new DefaultChannelRegistry(); + SingleChannelRouter router = new SingleChannelRouter(); + router.setChannelNameResolver(channelNameResolver); + router.setChannelRegistry(channelRegistry); + router.setResolutionRequired(true); + router.afterPropertiesSet(); + Message message = new StringMessage("123", "test"); + router.handle(message); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/util/RandomGuidUidGeneratorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/util/RandomGuidUidGeneratorTests.java new file mode 100644 index 0000000000..28ecf278c8 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/util/RandomGuidUidGeneratorTests.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.util; + +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; + +/** + * @author Mark Fisher + */ +public class RandomGuidUidGeneratorTests { + + @Test + public void testGeneratedIdIsNotNull() { + UidGenerator generator = new RandomGuidUidGenerator(); + Object id = generator.generateUid(); + assertNotNull(id); + } + +} diff --git a/spring-integration-docs/build.xml b/spring-integration-docs/build.xml new file mode 100644 index 0000000000..8bab5f7c01 --- /dev/null +++ b/spring-integration-docs/build.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/spring-integration-docs/reference/src/overview.xml b/spring-integration-docs/reference/src/overview.xml new file mode 100644 index 0000000000..bc949ce403 --- /dev/null +++ b/spring-integration-docs/reference/src/overview.xml @@ -0,0 +1,3 @@ + +TODO + \ No newline at end of file diff --git a/spring-integration-docs/reference/src/spring-integration-docs.xml b/spring-integration-docs/reference/src/spring-integration-docs.xml new file mode 100644 index 0000000000..1e79b012d0 --- /dev/null +++ b/spring-integration-docs/reference/src/spring-integration-docs.xml @@ -0,0 +1,36 @@ + + +]> + + + Reference Manual + + Spring Integration + 1.0 Milestone 1 + + + + + + + + + + + + + Mark + Fisher + + + + Copyright © SpringSource Inc., 2007 + + + + + &overview; + + \ No newline at end of file diff --git a/spring-integration-samples/.classpath b/spring-integration-samples/.classpath new file mode 100644 index 0000000000..4cbf810896 --- /dev/null +++ b/spring-integration-samples/.classpath @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-samples/.project b/spring-integration-samples/.project new file mode 100644 index 0000000000..f7934500f1 --- /dev/null +++ b/spring-integration-samples/.project @@ -0,0 +1,17 @@ + + + spring-integration-samples + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/spring-integration-samples/build.xml b/spring-integration-samples/build.xml new file mode 100644 index 0000000000..ea601c25e4 --- /dev/null +++ b/spring-integration-samples/build.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/spring-integration-samples/ivy.xml b/spring-integration-samples/ivy.xml new file mode 100644 index 0000000000..e162c0b0ed --- /dev/null +++ b/spring-integration-samples/ivy.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-integration-samples/resources/changelog.txt b/spring-integration-samples/resources/changelog.txt new file mode 100644 index 0000000000..e42590aa7e --- /dev/null +++ b/spring-integration-samples/resources/changelog.txt @@ -0,0 +1,6 @@ +SPRING INTEGRATION SAMPLES CHANGELOG +==================================== + +Changes in version 1.0 M1 (Jan 04, 2008) +---------------------------------------- + diff --git a/spring-integration-samples/resources/license.txt b/spring-integration-samples/resources/license.txt new file mode 100644 index 0000000000..29f81d812f --- /dev/null +++ b/spring-integration-samples/resources/license.txt @@ -0,0 +1,201 @@ + 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/spring-integration-samples/resources/notice.txt b/spring-integration-samples/resources/notice.txt new file mode 100644 index 0000000000..4cb43e0bd3 --- /dev/null +++ b/spring-integration-samples/resources/notice.txt @@ -0,0 +1,21 @@ + ======================================================================== + == NOTICE file corresponding to section 4 d of the Apache License, == + == Version 2.0, in this case for the Spring Integration distribution. == + ======================================================================== + + This product includes software developed by + the Apache Software Foundation (http://www.apache.org). + + The end-user documentation included with a redistribution, if any, + must include the following acknowledgement: + + "This product includes software developed by the Spring Framework + Project (http://www.springframework.org)." + + Alternatively, this acknowledgement may appear in the software itself, + if and wherever such third-party acknowledgements normally appear. + + The names "Spring", "Spring Framework", and "Spring Integration" must + not be used to endorse or promote products derived from this software + without prior written permission. For written permission, please contact + rod.johnson@springsource.com or juergen.hoeller@springsource.com. diff --git a/spring-integration-samples/resources/readme.txt b/spring-integration-samples/resources/readme.txt new file mode 100644 index 0000000000..7fb74f5d4e --- /dev/null +++ b/spring-integration-samples/resources/readme.txt @@ -0,0 +1,3 @@ +SPRING INTEGRATION SAMPLES 1.0 M1 (JAN 04 2008) +----------------------------------------------- + diff --git a/spring-integration-samples/src/main/java/org/springframework/integration/samples/filecopy/Exclaimer.java b/spring-integration-samples/src/main/java/org/springframework/integration/samples/filecopy/Exclaimer.java new file mode 100644 index 0000000000..b56b1e4db2 --- /dev/null +++ b/spring-integration-samples/src/main/java/org/springframework/integration/samples/filecopy/Exclaimer.java @@ -0,0 +1,28 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.samples.filecopy; + +/** + * @author Mark Fisher + */ +public class Exclaimer { + + public String exclaim(String input) { + return input.toUpperCase() + "!!!"; + } + +} diff --git a/spring-integration-samples/src/main/java/org/springframework/integration/samples/filecopy/FileCopyDemo.java b/spring-integration-samples/src/main/java/org/springframework/integration/samples/filecopy/FileCopyDemo.java new file mode 100644 index 0000000000..a5954548ea --- /dev/null +++ b/spring-integration-samples/src/main/java/org/springframework/integration/samples/filecopy/FileCopyDemo.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.samples.filecopy; + +import java.io.File; + +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * Demo of file source and target adapters. + * + * @author Mark Fisher + */ +public class FileCopyDemo { + + public static void main(String[] args) { + setupDirectories(); + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("fileCopyDemo.xml", FileCopyDemo.class); + context.start(); + } + + private static void setupDirectories() { + String tmpDirPath = System.getProperty("java.io.tmpdir"); + File parentDir = new File(tmpDirPath + File.separator + "spring-integration-samples"); + File inDir = new File(parentDir, "input"); + File outDir = new File(parentDir, "output"); + if ((inDir.exists() || inDir.mkdirs()) && (outDir.exists() || outDir.mkdirs())) { + System.out.println("input directory is: " + inDir.getAbsolutePath()); + System.out.println("output directory is: " + outDir.getAbsolutePath()); + } + else { + System.err.println("failed to create directories within tmp dir: " + tmpDirPath); + System.exit(0); + } + } + +} diff --git a/spring-integration-samples/src/main/java/org/springframework/integration/samples/filecopy/fileCopyDemo.xml b/spring-integration-samples/src/main/java/org/springframework/integration/samples/filecopy/fileCopyDemo.xml new file mode 100644 index 0000000000..fe4d2c2365 --- /dev/null +++ b/spring-integration-samples/src/main/java/org/springframework/integration/samples/filecopy/fileCopyDemo.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-samples/src/main/java/org/springframework/integration/samples/helloworld/HelloService.java b/spring-integration-samples/src/main/java/org/springframework/integration/samples/helloworld/HelloService.java new file mode 100644 index 0000000000..445fa7b55c --- /dev/null +++ b/spring-integration-samples/src/main/java/org/springframework/integration/samples/helloworld/HelloService.java @@ -0,0 +1,28 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.samples.helloworld; + +/** + * @author Mark Fisher + */ +public class HelloService { + + public String sayHello(String name) { + return "Hello " + name; + } + +} diff --git a/spring-integration-samples/src/main/java/org/springframework/integration/samples/helloworld/HelloWorldDemo.java b/spring-integration-samples/src/main/java/org/springframework/integration/samples/helloworld/HelloWorldDemo.java new file mode 100644 index 0000000000..60053ea54e --- /dev/null +++ b/spring-integration-samples/src/main/java/org/springframework/integration/samples/helloworld/HelloWorldDemo.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.samples.helloworld; + +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.channel.ChannelRegistry; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.config.MessageBusParser; +import org.springframework.integration.message.StringMessage; + +/** + * Demonstrates a basic message endpoint. + * + * @author Mark Fisher + */ +public class HelloWorldDemo { + + public static void main(String[] args) { + AbstractApplicationContext context = new ClassPathXmlApplicationContext("helloWorldDemo.xml", HelloWorldDemo.class); + context.start(); + ChannelRegistry channelRegistry = (ChannelRegistry) context.getBean(MessageBusParser.MESSAGE_BUS_BEAN_NAME); + MessageChannel inputChannel = channelRegistry.lookupChannel("inputChannel"); + MessageChannel outputChannel = channelRegistry.lookupChannel("outputChannel"); + inputChannel.send(new StringMessage(1, "World")); + System.out.println(outputChannel.receive().getPayload()); + context.stop(); + } + +} diff --git a/spring-integration-samples/src/main/java/org/springframework/integration/samples/helloworld/helloWorldDemo.xml b/spring-integration-samples/src/main/java/org/springframework/integration/samples/helloworld/helloWorldDemo.xml new file mode 100644 index 0000000000..31ab8da15e --- /dev/null +++ b/spring-integration-samples/src/main/java/org/springframework/integration/samples/helloworld/helloWorldDemo.xml @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/spring-integration-samples/src/main/java/org/springframework/integration/samples/oddeven/Counter.java b/spring-integration-samples/src/main/java/org/springframework/integration/samples/oddeven/Counter.java new file mode 100644 index 0000000000..50787539c1 --- /dev/null +++ b/spring-integration-samples/src/main/java/org/springframework/integration/samples/oddeven/Counter.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.samples.oddeven; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.springframework.integration.annotation.MessageEndpoint; +import org.springframework.integration.annotation.Polled; +import org.springframework.integration.annotation.Router; + +/** + * @author Mark Fisher + */ +@MessageEndpoint +public class Counter { + + private AtomicInteger count = new AtomicInteger(); + + @Polled(period=3000) + public int getNumber() { + return count.incrementAndGet(); + } + + @Router + public String resolveChannel(int i) { + if (i % 2 == 0) { + return "even"; + } + return "odd"; + } + +} diff --git a/spring-integration-samples/src/main/java/org/springframework/integration/samples/oddeven/NumberLogger.java b/spring-integration-samples/src/main/java/org/springframework/integration/samples/oddeven/NumberLogger.java new file mode 100644 index 0000000000..d94e918fb9 --- /dev/null +++ b/spring-integration-samples/src/main/java/org/springframework/integration/samples/oddeven/NumberLogger.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.samples.oddeven; + +import org.springframework.integration.annotation.Subscriber; +import org.springframework.stereotype.Component; + +/** + * @author Mark Fisher + */ +@Component +public class NumberLogger { + + @Subscriber(channel="even") + public void even(int i) { + System.out.println("even: " + i); + } + + @Subscriber(channel="odd") + public void odd(int i) { + System.out.println("odd: " + i); + } + +} diff --git a/spring-integration-samples/src/main/java/org/springframework/integration/samples/oddeven/OddEvenDemo.java b/spring-integration-samples/src/main/java/org/springframework/integration/samples/oddeven/OddEvenDemo.java new file mode 100644 index 0000000000..e7e30eda61 --- /dev/null +++ b/spring-integration-samples/src/main/java/org/springframework/integration/samples/oddeven/OddEvenDemo.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.samples.oddeven; + +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @author Mark Fisher + */ +public class OddEvenDemo { + + public static void main(String[] args) { + AbstractApplicationContext context = new ClassPathXmlApplicationContext("oddEvenDemo.xml", OddEvenDemo.class); + context.start(); + } + +} diff --git a/spring-integration-samples/src/main/java/org/springframework/integration/samples/oddeven/oddEvenDemo.xml b/spring-integration-samples/src/main/java/org/springframework/integration/samples/oddeven/oddEvenDemo.xml new file mode 100644 index 0000000000..bd899a335d --- /dev/null +++ b/spring-integration-samples/src/main/java/org/springframework/integration/samples/oddeven/oddEvenDemo.xml @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/spring-integration-samples/src/main/java/org/springframework/integration/samples/quote/Quote.java b/spring-integration-samples/src/main/java/org/springframework/integration/samples/quote/Quote.java new file mode 100644 index 0000000000..f655f02cf6 --- /dev/null +++ b/spring-integration-samples/src/main/java/org/springframework/integration/samples/quote/Quote.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.samples.quote; + +import java.math.BigDecimal; + +/** + * @author Mark Fisher + */ +public class Quote { + + private String ticker; + + private BigDecimal price; + + public Quote(String ticker, BigDecimal price) { + this.ticker = ticker; + this.price = price; + } + + public String toString() { + return ticker + ": " + price; + } + +} diff --git a/spring-integration-samples/src/main/java/org/springframework/integration/samples/quote/QuoteDemo.java b/spring-integration-samples/src/main/java/org/springframework/integration/samples/quote/QuoteDemo.java new file mode 100644 index 0000000000..09879c8711 --- /dev/null +++ b/spring-integration-samples/src/main/java/org/springframework/integration/samples/quote/QuoteDemo.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.samples.quote; + +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @author Mark Fisher + */ +public class QuoteDemo { + + public static void main(String[] args) { + AbstractApplicationContext context = new ClassPathXmlApplicationContext("quoteDemo.xml", QuoteDemo.class); + context.start(); + } +} diff --git a/spring-integration-samples/src/main/java/org/springframework/integration/samples/quote/QuotePublisher.java b/spring-integration-samples/src/main/java/org/springframework/integration/samples/quote/QuotePublisher.java new file mode 100644 index 0000000000..69f787ee22 --- /dev/null +++ b/spring-integration-samples/src/main/java/org/springframework/integration/samples/quote/QuotePublisher.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.samples.quote; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Random; + +import org.springframework.integration.annotation.MessageEndpoint; +import org.springframework.integration.annotation.Polled; + +/** + * @author Mark Fisher + */ +@MessageEndpoint(defaultOutput="quotes") +public class QuotePublisher { + + @Polled(period=300) + public Quote getQuote() { + BigDecimal price = new BigDecimal(new Random().nextDouble() * 100); + return new Quote(generateTicker(), price.setScale(2, RoundingMode.HALF_EVEN)); + } + + private String generateTicker() { + char[] chars = new char[3]; + for (int i = 0; i < 3; i++) { + chars[i] = (char) (new Random().nextInt(25) + 65); + } + return new String(chars); + } + +} diff --git a/spring-integration-samples/src/main/java/org/springframework/integration/samples/quote/QuoteSubscriber.java b/spring-integration-samples/src/main/java/org/springframework/integration/samples/quote/QuoteSubscriber.java new file mode 100644 index 0000000000..7c8f577bfa --- /dev/null +++ b/spring-integration-samples/src/main/java/org/springframework/integration/samples/quote/QuoteSubscriber.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.integration.samples.quote; + +import org.springframework.integration.annotation.Subscriber; + +/** + * @author Mark Fisher + */ +public class QuoteSubscriber { + + @Subscriber(channel="quotes") + public void log(Object o) { + System.out.println(o); + } + +} diff --git a/spring-integration-samples/src/main/java/org/springframework/integration/samples/quote/quoteDemo.xml b/spring-integration-samples/src/main/java/org/springframework/integration/samples/quote/quoteDemo.xml new file mode 100644 index 0000000000..cf66173db8 --- /dev/null +++ b/spring-integration-samples/src/main/java/org/springframework/integration/samples/quote/quoteDemo.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + +