commit 0d08676e161afb21b4e4c62985f8804fc20c5a4b Author: markpollack Date: Tue Jun 29 22:16:39 2010 -0400 initial commit diff --git a/.project b/.project new file mode 100644 index 00000000..0009b6a9 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + spring-amqp + + + + + + org.maven.ide.eclipse.maven2Builder + + + + + + org.maven.ide.eclipse.maven2Nature + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..c5ac800c --- /dev/null +++ b/pom.xml @@ -0,0 +1,196 @@ + + + 4.0.0 + org.springframework.amqp + spring-amqp + Spring AMQP + 1.0.0.BUILD-SNAPSHOT + pom + + spring-amqp-core + spring-amqp-parent + spring-otp + spring-rabbit + spring-rabbit-admin + + + UTF-8 + + + + staging + + + staging + file:///${java.io.tmpdir}/target/site + + + staging + file:///${java.io.tmpdir}/target/repository + + + staging + file:///${java.io.tmpdir}/target/snapshotRepository + + + + + + + http://static.springframework.org/spring-amqp/site/downloads/releases.html + + local + file://${java.io.tmpdir}/spring-amqp-repo + + + spring-snapshot + Spring Snapshot Repository + s3://maven.springframework.org/snapshot + + + static.springframework.org + scp://static.springframework.org/var/www/domains/springframework.org/static/htdocs/spring-amqp/sites/${project.version} + + + + + + + org.springframework.build.aws + org.springframework.build.aws.maven + 3.0.0.RELEASE + + + + + com.agilejava.docbkx + docbkx-maven-plugin + 2.0.6 + + + + generate-html + generate-pdf + + package + + + + + org.docbook + docbook-xml + 4.4 + runtime + + + + index.xml + true + ${project.basedir}/src/docbkx/resources/xsl/fopdf.xsl + + css/html.css + + false + ${project.basedir}/src/docbkx/resources/xsl/html.xsl + + + + version + ${pom.version} + + + + + + + + + + + + + + + + + + + + + + + maven-javadoc-plugin + 2.5 + + + aggregate + + aggregate + + package + + true + true +
Spring AMQP
+ 1.5 + true + ${project.basedir}/src/main/javadoc + ${project.basedir}/src/main/javadoc/overview.html + ${project.basedir}/src/main/javadoc/spring-javadoc.css + + true + + + Spring AMQP + org.springframework.amqp.core + + + + org.springframework.amqp.samples + + http://static.springframework.org/spring/docs/3.0.3.RELEASE/javadoc-api + http://java.sun.com/javase/6/docs/api + +
+
+
+
+ + maven-assembly-plugin + 2.2-beta-5 + false + + + distribution + + single + + package + + + ${project.basedir}/src/assembly/distribution.xml + + false + + + + +
+
+ + + + repository.springframework.maven.release + Spring Framework Maven Release Repository + http://maven.springframework.org/release + + +
diff --git a/spring-amqp-core/.classpath b/spring-amqp-core/.classpath new file mode 100644 index 00000000..7a0eedb4 --- /dev/null +++ b/spring-amqp-core/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/spring-amqp-core/.project b/spring-amqp-core/.project new file mode 100644 index 00000000..438fa2f0 --- /dev/null +++ b/spring-amqp-core/.project @@ -0,0 +1,35 @@ + + + spring-amqp-core + + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.maven.ide.eclipse.maven2Builder + + + + + org.springframework.ide.eclipse.core.springbuilder + + + + + + org.springframework.ide.eclipse.core.springnature + org.maven.ide.eclipse.maven2Nature + org.eclipse.jdt.core.javanature + org.eclipse.wst.common.project.facet.core.nature + + diff --git a/spring-amqp-core/.settings/org.maven.ide.eclipse.prefs b/spring-amqp-core/.settings/org.maven.ide.eclipse.prefs new file mode 100644 index 00000000..3e1fd3f7 --- /dev/null +++ b/spring-amqp-core/.settings/org.maven.ide.eclipse.prefs @@ -0,0 +1,9 @@ +#Wed Jan 13 19:28:20 EST 2010 +activeProfiles= +eclipse.preferences.version=1 +fullBuildGoals=process-test-resources +includeModules=false +resolveWorkspaceProjects=true +resourceFilterGoals=process-resources resources\:testResources +skipCompilerPlugin=true +version=1 diff --git a/spring-amqp-core/.springBeans b/spring-amqp-core/.springBeans new file mode 100644 index 00000000..dbe284d5 --- /dev/null +++ b/spring-amqp-core/.springBeans @@ -0,0 +1,13 @@ + + + 1 + + + + + + + + + + diff --git a/spring-amqp-core/pom.xml b/spring-amqp-core/pom.xml new file mode 100644 index 00000000..022d6488 --- /dev/null +++ b/spring-amqp-core/pom.xml @@ -0,0 +1,100 @@ + + 4.0.0 + + org.springframework.amqp + spring-amqp-parent + 1.0.0.BUILD-SNAPSHOT + ../spring-amqp-parent/pom.xml + + spring-amqp-core + jar + Spring AMQP Core + + + + org.springframework + spring-oxm + + + org.springframework + spring-test + + + + + javax.annotation + jsr250-api + true + + + cglib + cglib-nodep + true + + + org.mockito + mockito-all + test + + + junit + junit + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-mapper-asl + + + + + org.slf4j + slf4j-api + + + org.slf4j + jcl-over-slf4j + runtime + + + org.slf4j + slf4j-log4j12 + runtime + + + log4j + log4j + + + javax.mail + mail + + + javax.jms + jms + + + com.sun.jdmk + jmxtools + + + com.sun.jmx + jmxri + + + runtime + + + + + + com.springsource.bundlor + com.springsource.bundlor.maven + + + + diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/AmqpException.java b/spring-amqp-core/src/main/java/org/springframework/amqp/AmqpException.java new file mode 100644 index 00000000..b2482301 --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/AmqpException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2010 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.amqp; + +/** + * Base RuntimeException for errors that occur when executing AMQP operations. + * + * @author Mark Fisher + */ +public class AmqpException extends RuntimeException { + + public AmqpException(String message) { + super(message); + } + + public AmqpException(Throwable cause) { + super(cause); + } + + public AmqpException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/AmqpIOException.java b/spring-amqp-core/src/main/java/org/springframework/amqp/AmqpIOException.java new file mode 100644 index 00000000..56cc202c --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/AmqpIOException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2010 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.amqp; + +import java.io.IOException; + +/** + * RuntimeException wrapper for an {@link IOException} which + * can be commonly thrown from AMQP operations. + * + * @author Mark Pollack + */ +public class AmqpIOException extends AmqpException { + + public AmqpIOException(IOException cause) { + super(cause); + } + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/AmqpIllegalStateException.java b/spring-amqp-core/src/main/java/org/springframework/amqp/AmqpIllegalStateException.java new file mode 100644 index 00000000..c7b2bbf5 --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/AmqpIllegalStateException.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2010 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.amqp; + +/** + * Equivalent of an IllegalStateException but within the AmqpException hierarchy. + * + * @author Mark Pollack + */ +public class AmqpIllegalStateException extends AmqpException { + + public AmqpIllegalStateException(String message) { + super(message); + } + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/AmqpUnsupportedEncodingException.java b/spring-amqp-core/src/main/java/org/springframework/amqp/AmqpUnsupportedEncodingException.java new file mode 100644 index 00000000..0e89487b --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/AmqpUnsupportedEncodingException.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2010 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.amqp; + +/** + * RuntimeException for unsupported encoding in an AMQP operation. + * + * @author Mark Pollack + */ +public class AmqpUnsupportedEncodingException extends AmqpException { + + public AmqpUnsupportedEncodingException(Throwable cause) { + super(cause); + } + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/UncategorizedAmqpException.java b/spring-amqp-core/src/main/java/org/springframework/amqp/UncategorizedAmqpException.java new file mode 100644 index 00000000..c50bdb21 --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/UncategorizedAmqpException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2010 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.amqp; + +/** + * A "catch-all" exception type within the AmqpException hierarchy + * when no more specific cause is known. + * + * @author Mark Pollack + */ +public class UncategorizedAmqpException extends AmqpException { + + public UncategorizedAmqpException(Throwable cause) { + super(cause); + } + + public UncategorizedAmqpException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/core/AbstractExchange.java b/spring-amqp-core/src/main/java/org/springframework/amqp/core/AbstractExchange.java new file mode 100644 index 00000000..f2e30944 --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/core/AbstractExchange.java @@ -0,0 +1,88 @@ +/* + * Copyright 2002-2010 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.amqp.core; + +import java.util.Map; + + +/** + * Define common properties for all exchange types. + * + * @author Mark Pollack + */ +public abstract class AbstractExchange { + + protected String name; + + private boolean passive = false; + + private boolean durable = false; + + private boolean autoDelete = false; + + private Map arguments = null; + + + public abstract ExchangeType getExchangeType(); + + public String getName() { + return name; + } + + public boolean isPassive() { + return passive; + } + + public void setPassive(boolean passive) { + this.passive = passive; + } + + public boolean isDurable() { + return durable; + } + + public void setDurable(boolean durable) { + this.durable = durable; + } + + public boolean isAutoDelete() { + return autoDelete; + } + + public void setAutoDelete(boolean autoDelete) { + this.autoDelete = autoDelete; + } + + public Map getArguments() { + return arguments; + } + + public void setArguments(Map arguments) { + this.arguments = arguments; + } + + @Override + public String toString() { + return "Exchange [name=" + name + + ", type=" + getExchangeType().name() + + ", passive=" + passive + + ", durable=" + durable + + ", autoDelete=" + autoDelete + + ", arguments=" + arguments + "]"; + } + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/core/Address.java b/spring-amqp-core/src/main/java/org/springframework/amqp/core/Address.java new file mode 100644 index 00000000..ddbb18bf --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/core/Address.java @@ -0,0 +1,105 @@ +/* + * Copyright 2002-2010 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.amqp.core; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Represents an address for publication of an AMQP message. The AMQP 0-8 and + * 0-9 specifications have an unstructured string that is used as a "reply to" + * address. There are however conventions in use and this class makes it easier + * to follow these conventions. + * + * @author Mark Pollack + */ +public class Address { + + private static final Pattern pattern = Pattern.compile("^([^:]+)://([^/]*)/(.*)$"); + + + private String unstructuredAddress; + + private ExchangeType exchangeType; + + private String exchangeName; + + private String routingKey; + + private boolean structured; + + + /** + * Create an Address instance from an unstructured String. + * @param address an unstructured string. + */ + public Address(String address) { + this.unstructuredAddress = address; + } + + /*** + * Create an Address given the exchange type, exchange name and routing key. This + * will set the + * @param exchangeType + * @param exchangeName + * @param routingKey + */ + public Address(ExchangeType exchangeType, String exchangeName, String routingKey) { + this.structured = true; + this.exchangeType = exchangeType; + this.exchangeName = exchangeName; + this.routingKey = routingKey; + } + + + public static Address parse(String address) { + if (address == null) { + return null; + } + Matcher matcher = pattern.matcher(address); + boolean matchFound = matcher.find(); + if (matchFound) { + //TODO regex not expecting as would like with grouping. + String exchangeType = matcher.group(1); + String routingKey = address.substring(address.lastIndexOf(matcher.group(2))+matcher.group(2).length()+1); + return new Address(ExchangeType.valueOf(exchangeType), matcher.group(2),routingKey); + } + return new Address(address); + } + + public ExchangeType getExchangeType() { + return exchangeType; + } + + public String getExchangeName() { + return exchangeName; + } + + public String getRoutingKey() { + return routingKey; + } + + public boolean isStructured() { + return structured; + } + + public String toString() { + return this.structured ? this.exchangeType + "://" + this.exchangeName + "/" + this.routingKey + : this.unstructuredAddress; + } + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/core/AmqpTemplate.java b/spring-amqp-core/src/main/java/org/springframework/amqp/core/AmqpTemplate.java new file mode 100644 index 00000000..7e8e1e4f --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/core/AmqpTemplate.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2010 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.amqp.core; + +import org.springframework.amqp.AmqpException; + +/** + * Specifies a basic set of AMQP operations + * + * Implemented by {@link RabbitTemplate}. Not often used but a useful option to + * enhance testability, as it can easily be mocked or stubbed. + * + * Provides synchronous send an receive methods. The convertAndSend and receiveAndConvert + * methods allow let you send and receive POJO objects. RabbitTemplate delegates to + * an instance of {@link org.springframework.support.converter.MessageConverter} to perform + * conversion to and from Rabbit's byte[] payload type. + * + * @author Mark Pollack + * @author Mark Fisher + */ +public interface AmqpTemplate { + + void send(MessageCreator messageCreator) throws AmqpException; + + void send(String routingKey, MessageCreator messageCreator) throws AmqpException; + + void send(String exchange, String routingKey, MessageCreator messageCreator) throws AmqpException; + + void convertAndSend(Object message) throws AmqpException; + + void convertAndSend(String routingKey, Object message) throws AmqpException; + + void convertAndSend(String exchange, String routingKey, Object message) throws AmqpException; + + void convertAndSend(Object message, MessagePostProcessor messagePostProcessor) throws AmqpException; + + void convertAndSend(String routingKey, Object message, MessagePostProcessor messagePostProcessor) throws AmqpException; + + void convertAndSend(String exchange, String routingKey, Object message, MessagePostProcessor messagePostProcessor) throws AmqpException; + + Message receive() throws AmqpException; + + Message receive(String queueName) throws AmqpException; + + Object receiveAndConvert() throws AmqpException; + + Object receiveAndConvert(String queueName) throws AmqpException; + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/core/Binding.java b/spring-amqp-core/src/main/java/org/springframework/amqp/core/Binding.java new file mode 100644 index 00000000..4b57f8c6 --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/core/Binding.java @@ -0,0 +1,103 @@ +/* + * Copyright 2002-2010 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.amqp.core; + +import java.util.Map; + +/** + * Simple container collecting information to describe a queue binding. Takes Queue and Exchange + * class as arguments to facilitate wiring using @Bean code based configuration. + * Used in conjunction with RabbitAdminTemplate. + * + * @author Mark Pollack + * @see RabbitAdminTemplate + */ +public class Binding { + + private String queue; + + private String exchange; + + private String routingKey; + + Map arguments; + + + //Is this really worth the syntax sugar? + public static Binding binding(Queue queue, AbstractExchange exchange, String routingKey) { + return new Binding(queue, exchange, routingKey); + } + + //TODO would be good to remove this because invalid bindings can be specified (fanout with routing key) + Binding(Queue queue, AbstractExchange exchange, String routingKey) { + this.queue = queue.getName(); + this.exchange = exchange.getName(); + this.routingKey = routingKey; + } + + public Binding(Queue queue, FanoutExchange exchange) { + this.queue = queue.getName(); + this.exchange = exchange.getName(); + this.routingKey = ""; + } + + public Binding(Queue queue, DirectExchange exchange, String routingKey) { + this.queue = queue.getName(); + this.exchange = exchange.getName(); + this.routingKey = routingKey; + } + + public Binding(Queue queue, DirectExchange exchange) { + this.queue = queue.getName(); + this.exchange = exchange.getName(); + this.routingKey = ""; + } + + + public Binding(Queue queue, TopicExchange exchange, String routingKey) { + this.queue = queue.getName(); + this.exchange = exchange.getName(); + this.routingKey = routingKey; + } + + public Binding(Queue queue, TopicExchange exchange) { + this.queue = queue.getName(); + this.exchange = exchange.getName(); + this.routingKey = ""; + } + + public String getQueue() { + return queue; + } + + public String getExchange() { + return exchange; + } + + public String getRoutingKey() { + return routingKey; + } + + public Map getArguments() { + return arguments; + } + + public void setArguments(Map arguments) { + this.arguments = arguments; + } + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/core/BindingBuilder.java b/spring-amqp-core/src/main/java/org/springframework/amqp/core/BindingBuilder.java new file mode 100644 index 00000000..ac9e389c --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/core/BindingBuilder.java @@ -0,0 +1,62 @@ +/* + * Copyright 2002-2010 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.amqp.core; + + +/** + * Basic builder class to create bindings for a more fluent API style in code based configuration. + * + * @author Mark Pollack + * @author Mark Fisher + */ +public final class BindingBuilder { + + private Queue queue; + + private AbstractExchange exchange; + + private String routingKey; + + //return intermediate object that only allows to call 'to' to configure the binding. + public static BindingBuilder from(Queue queue) { + return new BindingBuilder(queue); + } + + + private BindingBuilder(Queue queue) { + super(); + this.queue = queue; + } + + //return intermediate object that only allows to call 'with' to configure the binding. + public BindingBuilder to(AbstractExchange exchange) { + this.exchange = exchange; + return this; + } + + // creates the product. + public Binding with(String routingKey) { + this.routingKey = routingKey; + return new Binding(this.queue, this.exchange, this.routingKey); + } + + public Binding with(Enum routingKeyEnum) { + this.routingKey = routingKeyEnum.toString(); + return new Binding(this.queue, this.exchange, this.routingKey); + } + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/core/DirectExchange.java b/spring-amqp-core/src/main/java/org/springframework/amqp/core/DirectExchange.java new file mode 100644 index 00000000..ab1afe10 --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/core/DirectExchange.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2010 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.amqp.core; + + + +/** + * Simple container collecting information to describe a direct exchange. + * Used in conjunction with RabbitAdminTemplate. + * + * @author Mark Pollack + * @see RabbitAdminTemplate + */ +public class DirectExchange extends AbstractExchange { + + public static final DirectExchange DEFAULT = new DirectExchange(""); + + public DirectExchange(String name) { + this.name = name; + } + + public ExchangeType getExchangeType() { + return ExchangeType.direct; + } +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/core/ExchangeType.java b/spring-amqp-core/src/main/java/org/springframework/amqp/core/ExchangeType.java new file mode 100644 index 00000000..bd360f4d --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/core/ExchangeType.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2010 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.amqp.core; + +/** + * Enumeration of exchange types. + * TODO consider 'header' type? + * + * @author Mark Pollack + * + */ +public enum ExchangeType { + fanout(), direct(), topic() +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/core/FanoutExchange.java b/spring-amqp-core/src/main/java/org/springframework/amqp/core/FanoutExchange.java new file mode 100644 index 00000000..2a872980 --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/core/FanoutExchange.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2010 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.amqp.core; + + + +/** + * Simple container collecting information to describe a fanout exchange. + * Used in conjunction with RabbitAdminTemplate. + * + * @author Mark Pollack + * @see RabbitAdminTemplate + */ +public class FanoutExchange extends AbstractExchange { + + public FanoutExchange(String name) { + this.name = name; + } + + public ExchangeType getExchangeType() { + return ExchangeType.fanout; + } +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/core/Message.java b/spring-amqp-core/src/main/java/org/springframework/amqp/core/Message.java new file mode 100644 index 00000000..0a3de69f --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/core/Message.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2010 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.amqp.core; + +import org.springframework.amqp.utils.SerializationUtils; + +/** + * @author Mark Pollack + * @author Mark Fisher + * @author Oleg Zhurakousky + */ +public class Message { + + private final MessageProperties messageProperties; + + private final byte[] body; + + + public Message(byte[] body, MessageProperties messageProperties) { + this.body = body; + this.messageProperties = messageProperties; + } + + + public byte[] getBody() { + return this.body; + } + + public MessageProperties getMessageProperties() { + return this.messageProperties; + } + + public String toString() { + StringBuffer buffer = new StringBuffer(); + buffer.append("("); + buffer.append("Body:'" + this.getBodyContentAsString() + "';"); + buffer.append("ID:" + messageProperties.getMessageId() + ";"); + buffer.append("Content:" + messageProperties.getContentType() + ";"); + buffer.append("Headers:" + messageProperties.getHeaders() + ";"); + buffer.append("Exchange:" + messageProperties.getReceivedExchange() + ";"); + buffer.append("Routing key:" + messageProperties.getReceivedRoutingKey() + ";"); + buffer.append("Reply:" + messageProperties.getReplyTo() + ";"); + buffer.append("Delivery mode:" + messageProperties.getDeliveryMode() + ";"); + buffer.append(")"); + return buffer.toString(); + } + + private String getBodyContentAsString() { + String contentType = messageProperties.getContentType(); + if (contentType.equals(MessageProperties.CONTENT_TYPE_SERIALIZED_OBJECT)) { + return (String) SerializationUtils.deserialize(body); + } + else if (contentType.equals(MessageProperties.CONTENT_TYPE_BYTES)) { + return body.toString(); + } + else if (contentType.equals(MessageProperties.CONTENT_TYPE_TEXT_PLAIN)) { + return new String(body); + } + else if (contentType.equals(MessageProperties.CONTENT_TYPE_JSON)) { + //TODO provide JSON conversion to string + return body.toString(); + } + return null; + } + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/core/MessageCreator.java b/spring-amqp-core/src/main/java/org/springframework/amqp/core/MessageCreator.java new file mode 100644 index 00000000..ab54f1b3 --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/core/MessageCreator.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2010 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.amqp.core; + +/** + * Strategy interface for creating RabbitMQ messages. + * + * @author Mark Fisher + */ +public interface MessageCreator { + + /** + * Create a {@link Message} to be sent. + */ + Message createMessage(); + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/core/MessageDeliveryMode.java b/spring-amqp-core/src/main/java/org/springframework/amqp/core/MessageDeliveryMode.java new file mode 100644 index 00000000..f6b27834 --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/core/MessageDeliveryMode.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2010 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.amqp.core; + +/** + * Enumeration for the message delivery mode. Can be persistent or + * non persistent. Use the method 'toInt' to get the appropriate value + * that is used the he AMQP protocol instead of the ordinal() value when + * passing into AMQP APIs. + * + * @author Mark Pollack + * + */ +public enum MessageDeliveryMode { + + NON_PERSISTENT, PERSISTENT; + + public static int toInt(MessageDeliveryMode mode) { + switch (mode) { + case NON_PERSISTENT: { + return 1; + } + case PERSISTENT: { + return 2; + } + default: { + return -1; + } + } + } + + public static MessageDeliveryMode fromInt(int modeAsNumber) { + switch (modeAsNumber) { + case 1: { + return NON_PERSISTENT; + } + case 2: { + return PERSISTENT; + } + default: { + return null; + } + } + } + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/core/MessageListener.java b/spring-amqp-core/src/main/java/org/springframework/amqp/core/MessageListener.java new file mode 100644 index 00000000..64d7c3a6 --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/core/MessageListener.java @@ -0,0 +1,28 @@ +/* + * Copyright 2002-2010 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.amqp.core; + +/** + * Listener interface to receive asynchronous delivery of Amqp Messages. + * + * @author Mark Pollack + */ +public interface MessageListener { + + void onMessage(Message message); + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/core/MessagePostProcessor.java b/spring-amqp-core/src/main/java/org/springframework/amqp/core/MessagePostProcessor.java new file mode 100644 index 00000000..d74655bd --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/core/MessagePostProcessor.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2010 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.amqp.core; + +import org.springframework.amqp.AmqpException; + +/** + * To be used with the send method of Amqp template classes (such as RabbitTemplate) + * that convert an object to a message. + * It allows for further modification of the message after it has been processed + * by the converter. This is useful for setting of Header and Properties. + * + *

This often as an anonymous class within a method implementation. + * @author Mark Pollack + */ +public interface MessagePostProcessor { + + Message postProcessMessage(Message message) throws AmqpException; + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/core/MessageProperties.java b/spring-amqp-core/src/main/java/org/springframework/amqp/core/MessageProperties.java new file mode 100644 index 00000000..92cfe08e --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/core/MessageProperties.java @@ -0,0 +1,126 @@ +/* + * Copyright 2002-2010 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.amqp.core; + +import java.util.Date; +import java.util.Map; + +/** + * Message Properties for an AMQP message. + * + * @author Mark Fisher + * @author Mark Pollack + * + */ +public interface MessageProperties { + + public static final String CONTENT_TYPE_BYTES = "application/octet-stream"; + public static final String CONTENT_TYPE_TEXT_PLAIN = "text/plain"; + public static final String CONTENT_TYPE_SERIALIZED_OBJECT = "application/x-java-serialized-object"; + public static final String CONTENT_TYPE_JSON = "application/json"; + + public abstract Map getHeaders(); + + public abstract void setHeader(String key, Object value); + + //NOTE qpid java timestamp is long, presumably can convert to Date. + public abstract Date getTimestamp(); + + public abstract void setAppId(String appId); + + public abstract String getAppId(); + + public abstract void setUserId(String userId); + + //NOTE Note forward compatible with qpid 1.0 .NET + // qpid 0.8 .NET/java: is a string + // qpid 1.0 .NET: getUserId is byte[] + public abstract String getUserId(); + + public abstract void setType(String type); + + //TODO what is this? is it stuctureType - int in qpid + public abstract String getType(); + + //NOTE Not forward compatible with qpid 1.0 .NET + // qpid 0.8 .NET/Java: is a string + // qpid 1.0 .NET: MessageId property on class MessageProperties and is UUID + // There is an 'ID' stored IMessage class and is an int. + public abstract void setMessageId(String id); + + public abstract String getMessageId(); + + + //NOTE not foward compatible with qpid 1.0 .NET + // qpid 0.8 .NET/Java: is a string + // qpid 1.0 .NET: is not present + public abstract void setClusterId(String id); + + public abstract String getClusterId(); + + public abstract void setCorrelationId(byte[] correlationId); + + public abstract byte[] getCorrelationId(); + + public abstract void setReplyTo(Address replyTo); + + //TODO - create Address/ReplyTo class to encapsulate exchangType/exchange/routingkey ? + // qpid 0.8/1.0 .NET don't use a single string, but a pair. + // qpid 0.8 Java uses a single string + // + // See RabbitMQ .NET developer guide for more details on conventions for this string + public abstract Address getReplyTo(); + + public abstract void setContentType(String contentType); + + public abstract String getContentType(); + + public abstract void setContentEncoding(String contentEncoding); + + public abstract String getContentEncoding(); + + public abstract void setContentLength(long contentLength); + + public abstract long getContentLength(); + + public abstract void setDefaultCharset(String charSet); + + public abstract void setDeliveryMode(MessageDeliveryMode deliveryMode); + + public abstract MessageDeliveryMode getDeliveryMode(); + + public abstract void setExpiration(String expiration); + + //NOTE qpid Java broker qpid 0.8/1.0 .NET: is a long. + // 0.8 Spec has: expiration (shortstr) + public abstract String getExpiration(); + + public abstract void setPriority(Integer priority); + + public abstract Integer getPriority(); + + public abstract String getReceivedExchange(); + + public abstract String getReceivedRoutingKey(); + + public abstract Boolean isRedelivered(); + + public abstract long getDeliveryTag(); + + public abstract Integer getMessageCount(); + +} \ No newline at end of file diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/core/Queue.java b/spring-amqp-core/src/main/java/org/springframework/amqp/core/Queue.java new file mode 100644 index 00000000..d21025a7 --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/core/Queue.java @@ -0,0 +1,91 @@ +/* + * Copyright 2002-2010 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.amqp.core; + +/** + * Simple container collecting information to describe a queue. + * Used in conjunction with RabbitAdminTemplate. + * + * @author Mark Pollack + * @see RabbitAdminTemplate + */ +public class Queue { + + private String name; + + private boolean passive; + + private boolean durable; + + private boolean exclusive; + + private boolean autoDelete; + + private java.util.Map arguments; + + public Queue(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public boolean isPassive() { + return passive; + } + + public void setPassive(boolean passive) { + this.passive = passive; + } + + public boolean isDurable() { + return durable; + } + + public void setDurable(boolean durable) { + this.durable = durable; + } + + public boolean isExclusive() { + return exclusive; + } + + public void setExclusive(boolean exclusive) { + this.exclusive = exclusive; + } + + public boolean isAutoDelete() { + return autoDelete; + } + + public void setAutoDelete(boolean autoDelete) { + this.autoDelete = autoDelete; + } + + public java.util.Map getArguments() { + return arguments; + } + + public void setArguments( + java.util.Map arguments) { + this.arguments = arguments; + } + + + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/core/SimpleMessageProperties.java b/spring-amqp-core/src/main/java/org/springframework/amqp/core/SimpleMessageProperties.java new file mode 100644 index 00000000..784b26a2 --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/core/SimpleMessageProperties.java @@ -0,0 +1,242 @@ +/* + * Copyright 2002-2010 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.amqp.core; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Mark Fisher + */ +public class SimpleMessageProperties implements MessageProperties { + + private static final String DEFAULT_CHARSET = "UTF-8"; + + + private volatile String defaultCharset = DEFAULT_CHARSET; + + private volatile String appId; + + private volatile String clusterId; + + private volatile String contentEncoding; + + private volatile long contentLength; + + private volatile String contentType = MessageProperties.CONTENT_TYPE_BYTES; + + private volatile byte[] correlationId; + + private volatile MessageDeliveryMode deliveryMode; + + private volatile long deliveryTag; + + private volatile String expiration; + + private volatile Map headers = new HashMap(); + + private volatile Integer messageCount; + + private volatile String messageId; + + private volatile Integer priority; + + private volatile String receivedExchange; + + private volatile String receivedRoutingKey; + + private volatile Address replyTo; + + private volatile Date timestamp; + + private volatile String type; + + private volatile String userId; + + private volatile Boolean redelivered; + + + public String getAppId() { + return this.appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getClusterId() { + return this.clusterId; + } + + public void setClusterId(String clusterId) { + this.clusterId = clusterId; + } + + public String getContentEncoding() { + return this.contentEncoding; + } + + public void setContentEncoding(String contentEncoding) { + this.contentEncoding = contentEncoding; + } + + public long getContentLength() { + return this.contentLength; + } + + public void setContentLength(long contentLength) { + this.contentLength = contentLength; + } + + public String getContentType() { + return this.contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public byte[] getCorrelationId() { + return this.correlationId; + } + + public void setCorrelationId(byte[] correlationId) { + this.correlationId = correlationId; + } + + public MessageDeliveryMode getDeliveryMode() { + return this.deliveryMode; + } + + public void setDeliveryMode(MessageDeliveryMode deliveryMode) { + this.deliveryMode = deliveryMode; + } + + public long getDeliveryTag() { + return this.deliveryTag; + } + + public void setDeliveryTag(long deliveryTag) { + this.deliveryTag = deliveryTag; + } + + public String getExpiration() { + return this.expiration; + } + + public void setExpiration(String expiration) { + this.expiration = expiration; + } + + public Map getHeaders() { + return this.headers; + } + + public void setHeaders(Map headers) { + this.headers = headers; + } + + public Integer getMessageCount() { + return this.messageCount; + } + + public void setMessageCount(Integer messageCount) { + this.messageCount = messageCount; + } + + public String getMessageId() { + return this.messageId; + } + + public void setMessageId(String messageId) { + this.messageId = messageId; + } + + public Integer getPriority() { + return this.priority; + } + + public void setPriority(Integer priority) { + this.priority = priority; + } + + public String getReceivedExchange() { + return this.receivedExchange; + } + + public void setReceivedExchange(String receivedExchange) { + this.receivedExchange = receivedExchange; + } + + public String getReceivedRoutingKey() { + return this.receivedRoutingKey; + } + + public void setReceivedRoutingKey(String receivedRoutingKey) { + this.receivedRoutingKey = receivedRoutingKey; + } + + public Address getReplyTo() { + return this.replyTo; + } + + public void setReplyTo(Address replyTo) { + this.replyTo = replyTo; + } + + public Date getTimestamp() { + return this.timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + + public String getType() { + return this.type; + } + + public void setType(String type) { + this.type = type; + } + + public String getUserId() { + return this.userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public Boolean isRedelivered() { + return this.redelivered; + } + + public void setRedelivered(Boolean redelivered) { + this.redelivered = redelivered; + } + + public void setDefaultCharset(String defaultCharset) { + this.defaultCharset = defaultCharset; + } + + public void setHeader(String key, Object value) { + this.headers.put(key, value); + } + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/core/TopicExchange.java b/spring-amqp-core/src/main/java/org/springframework/amqp/core/TopicExchange.java new file mode 100644 index 00000000..9df2f5f6 --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/core/TopicExchange.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2010 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.amqp.core; + + + +/** + * Simple container collecting information to describe a topic exchange. + * Used in conjunction with RabbitAdminTemplate. + * + * @author Mark Pollack + * @see RabbitAdminTemplate + */ +public class TopicExchange extends AbstractExchange { + + public TopicExchange(String name) { + this.name = name; + } + + public ExchangeType getExchangeType() { + return ExchangeType.topic; + } +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/core/UniquelyNamedQueue.java b/spring-amqp-core/src/main/java/org/springframework/amqp/core/UniquelyNamedQueue.java new file mode 100644 index 00000000..9f63d2a5 --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/core/UniquelyNamedQueue.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2010 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.amqp.core; + +import java.util.UUID; + +/** + * Create a queue with a unique name. + * + * @author Mark Pollack + * + */ +public class UniquelyNamedQueue extends Queue { + + /** + * Create a uniquely named queue. The name of the queue is a random UUID. + */ + public UniquelyNamedQueue() { + super(UUID.randomUUID().toString()); + } + + /** + * Create a uniquely named queue with a fixed prefix. The constructor argument specifies + * the prefix which is prepended to a random UUID. + * @param prefix + */ + public UniquelyNamedQueue(String prefix) { + super(prefix + "-" + UUID.randomUUID()); + } + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/support/converter/ClassMapper.java b/spring-amqp-core/src/main/java/org/springframework/amqp/support/converter/ClassMapper.java new file mode 100644 index 00000000..a414a142 --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/support/converter/ClassMapper.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2010 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.amqp.support.converter; + +/** + * Strategy for setting metadata on messages such that one can create the class that needs to + * be instantiated when receiving a message. + * + * @author Mark Pollack + * + */ +public interface ClassMapper { + + String getClassIdFieldName(); + + String fromClass(Class clazz); + + Class toClass(String classId); +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/support/converter/DefaultClassMapper.java b/spring-amqp-core/src/main/java/org/springframework/amqp/support/converter/DefaultClassMapper.java new file mode 100644 index 00000000..54b6ceba --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/support/converter/DefaultClassMapper.java @@ -0,0 +1,102 @@ +/* + * Copyright 2002-2010 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.amqp.support.converter; + +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; +import java.util.Map.Entry; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.ClassUtils; + +/** + * + * @author Mark Pollack + * + */ +public class DefaultClassMapper implements ClassMapper, InitializingBean { + + + public static final String DEFAULT_CLASSID_FIELD_NAME = "__TypeId__"; + + private Map idClassMapping = new HashMap(); + + private Map classIdMapping = new HashMap(); + + private String defaultHashtableTypeId = "Hashtable"; + + private Class defaultHashtableClass = Hashtable.class; + + + public void setDefaultHashtableClass(Class defaultHashtableClass) { + this.defaultHashtableClass = defaultHashtableClass; + } + + public String getClassIdFieldName() { + return DEFAULT_CLASSID_FIELD_NAME; + } + + public void setIdClassMapping(Map idClassMapping) { + this.idClassMapping = idClassMapping; + } + + public String fromClass(Class classOfObjectToConvert) { + if (classIdMapping.containsKey(classOfObjectToConvert)) { + return classIdMapping.get(classOfObjectToConvert); + } + if (Map.class.isAssignableFrom(classOfObjectToConvert)) + { + return this.defaultHashtableTypeId; + } + return classOfObjectToConvert.getName(); + } + + public Class toClass(String classId) { + if (this.idClassMapping.containsKey(classId)) { + return idClassMapping.get(classId); + } + if (classId.equals(this.defaultHashtableTypeId)) + { + return this.defaultHashtableClass; + } + try { + return ClassUtils.forName(classId, getClass().getClassLoader()); + } catch (ClassNotFoundException e) { + throw new MessageConversionException("failed to resolve class name [" + classId + "]", e); + } catch (LinkageError e) { + throw new MessageConversionException("failed to resolve class name [" + classId + "]", e); + } + } + + + + public void afterPropertiesSet() throws Exception { + validateIdTypeMapping(); + } + + private void validateIdTypeMapping() { + Map finalIdClassMapping = new HashMap(); + for (Entry entry : idClassMapping.entrySet()) { + String id = entry.getKey(); + Class clazz = entry.getValue(); + finalIdClassMapping.put(id, clazz); + classIdMapping.put(clazz, id); + } + this.idClassMapping = finalIdClassMapping; + } + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/support/converter/JsonMessageConverter.java b/spring-amqp-core/src/main/java/org/springframework/amqp/support/converter/JsonMessageConverter.java new file mode 100644 index 00000000..0c89096b --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/support/converter/JsonMessageConverter.java @@ -0,0 +1,168 @@ +/* + * Copyright 2002-2010 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.amqp.support.converter; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.map.DeserializationConfig; +import org.codehaus.jackson.map.JsonMappingException; +import org.codehaus.jackson.map.ObjectMapper; + +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; + +/** + * JSON converter that uses the Jackson Json library. + * + * @author Mark Pollack + */ +public class JsonMessageConverter implements MessageConverter { + + // TODO create composite MessageConverter with key/value pairs of content-type/converter. introduce base class + + private static Log log = LogFactory.getLog(JsonMessageConverter.class); + + public static final String DEFAULT_CHARSET = "UTF-8"; + + + private volatile String defaultCharset = DEFAULT_CHARSET; + + private ObjectMapper jsonObjectMapper = new ObjectMapper(); + + private ClassMapper classMapper = new DefaultClassMapper(); + + + public JsonMessageConverter() { + super(); + initializeJsonObjectMapper(); + } + + + /** + * Specify the default charset to use when converting to or from text-based + * Message body content. If not specified, the charset will be "UTF-8". + */ + public void setDefaultCharset(String defaultCharset) { + this.defaultCharset = (defaultCharset != null) ? defaultCharset : DEFAULT_CHARSET; + } + + public ClassMapper getClassMapper() { + return classMapper; + } + + public void setClassMapper(ClassMapper classMapper) { + this.classMapper = classMapper; + } + + /** + * Subclass and override to customize. + */ + protected void initializeJsonObjectMapper() { + jsonObjectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + + public Object fromMessage(Message message) throws MessageConversionException { + Object content = null; + MessageProperties properties = message.getMessageProperties(); + if (properties != null) { + String contentType = properties.getContentType(); + if (contentType != null && contentType.contains("json")) { + String encoding = properties.getContentEncoding(); + if (encoding == null) { + encoding = this.defaultCharset; + } + try { + //content = new String(message.getBody(), encoding); + Map headers = message.getMessageProperties().getHeaders(); + Object classIdFieldNameValue = headers.get(classMapper.getClassIdFieldName()); + String classId = null; + if (classIdFieldNameValue != null) { + classId = classIdFieldNameValue.toString(); + } + if (classId == null) { + throw new MessageConversionException( + "failed to convert json-based Message content. Could not resolve classId in header"); + } + Class targetClass = classMapper.toClass(classId); + content = convertBytesToObject(message.getBody(), encoding, targetClass); + } + catch (UnsupportedEncodingException e) { + throw new MessageConversionException("Failed to convert json-based Message content", e); + } + catch (JsonParseException e) { + throw new MessageConversionException("Failed to convert Message content", e); + } + catch (JsonMappingException e) { + throw new MessageConversionException("Failed to convert Message content", e); + } + catch (IOException e) { + throw new MessageConversionException("Failed to convert Message content", e); + } + } + else { + log.warn("Could not convert incoming message with content-type [" + contentType + "]"); + } + } + if (content == null) { + content = message.getBody(); + } + return content; + } + + + private Object convertBytesToObject(byte[] body, String encoding, Class targetClass) throws JsonParseException, JsonMappingException, IOException { + String contentAsString = new String(body, encoding); + return jsonObjectMapper.readValue(contentAsString, targetClass); + } + + public Message toMessage(Object objectToConvert, MessageProperties messageProperties) throws MessageConversionException { + byte[] bytes = null; + try { + String jsonString = jsonObjectMapper.writeValueAsString(objectToConvert); + bytes = jsonString.getBytes(this.defaultCharset); + } + catch (UnsupportedEncodingException e) { + throw new MessageConversionException("Failed to convert Message content", e); + } + catch (JsonGenerationException e) { + throw new MessageConversionException("Failed to convert Message content", e); + } + catch (JsonMappingException e) { + throw new MessageConversionException("Failed to convert Message content", e); + } + catch (IOException e) { + throw new MessageConversionException("Failed to convert Message content", e); + } + messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON); + messageProperties.setContentEncoding(this.defaultCharset); + if (bytes != null) { + messageProperties.setContentLength(bytes.length); + } + Map headers = messageProperties.getHeaders(); + headers.put(classMapper.getClassIdFieldName(), classMapper.fromClass(objectToConvert.getClass())); + return new Message(bytes, messageProperties); + } + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/support/converter/MarshallingMessageConverter.java b/spring-amqp-core/src/main/java/org/springframework/amqp/support/converter/MarshallingMessageConverter.java new file mode 100644 index 00000000..ecd96367 --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/support/converter/MarshallingMessageConverter.java @@ -0,0 +1,152 @@ +/* + * Copyright 2002-2010 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.amqp.support.converter; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.oxm.Marshaller; +import org.springframework.oxm.Unmarshaller; +import org.springframework.oxm.XmlMappingException; +import org.springframework.util.Assert; + +/** + * Spring Rabbit {@link MessageConverter} that uses a {@link Marshaller} and {@link Unmarshaller}. + * Marshals an object to a {@link Message} and unmarshals a {@link Message} to an object. + * + * @author Mark Fisher + * @author Arjen Poutsma + * @author Juergen Hoeller + * @see org.springframework.amqp.rabbit.core.RabbitTemplate#convertAndSend + * @see org.springframework.amqp.rabbit.core.RabbitTemplate#receiveAndConvert + */ +public class MarshallingMessageConverter implements MessageConverter, InitializingBean { + + private Marshaller marshaller; + + private Unmarshaller unmarshaller; + + + /** + * Construct a new MarshallingMessageConverter with no {@link Marshaller} or {@link Unmarshaller} set. + * The marshaller must be set after construction by invoking {@link #setMarshaller(Marshaller)} and + * {@link #setUnmarshaller(Unmarshaller)} . + */ + public MarshallingMessageConverter() { + } + + /** + * Construct a new MarshallingMessageConverter with the given {@link Marshaller} set. + *

If the given {@link Marshaller} also implements the {@link Unmarshaller} interface, + * it is used for both marshalling and unmarshalling. Otherwise, an exception is thrown. + *

Note that all {@link Marshaller} implementations in Spring also implement the + * {@link Unmarshaller} interface, so that you can safely use this constructor. + * @param marshaller object used as marshaller and unmarshaller + * @throws IllegalArgumentException when marshaller does not implement the + * {@link Unmarshaller} interface as well + */ + public MarshallingMessageConverter(Marshaller marshaller) { + Assert.notNull(marshaller, "marshaller must not be null"); + if (!(marshaller instanceof Unmarshaller)) { + throw new IllegalArgumentException( + "Marshaller [" + marshaller + "] does not implement the Unmarshaller " + + "interface. Please set an Unmarshaller explicitly by using the " + + "MarshallingMessageConverter(Marshaller, Unmarshaller) constructor."); + } + else { + this.marshaller = marshaller; + this.unmarshaller = (Unmarshaller) marshaller; + } + } + + /** + * Construct a new MarshallingMessageConverter with the + * given Marshaller and Unmarshaller. + * @param marshaller the Marshaller to use + * @param unmarshaller the Unmarshaller to use + */ + public MarshallingMessageConverter(Marshaller marshaller, Unmarshaller unmarshaller) { + Assert.notNull(marshaller, "marshaller must not be null"); + Assert.notNull(unmarshaller, "unmarshaller must not be null"); + this.marshaller = marshaller; + this.unmarshaller = unmarshaller; + } + + + /** + * Set the {@link Marshaller} to be used by this message converter. + */ + public void setMarshaller(Marshaller marshaller) { + this.marshaller = marshaller; + } + + /** + * Set the {@link Unmarshaller} to be used by this message converter. + */ + public void setUnmarshaller(Unmarshaller unmarshaller) { + this.unmarshaller = unmarshaller; + } + + public void afterPropertiesSet() { + Assert.notNull(this.marshaller, "Property 'marshaller' is required"); + Assert.notNull(this.unmarshaller, "Property 'unmarshaller' is required"); + } + + + /** + * Marshals the given object to a {@link Message}. + */ + public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + StreamResult streamResult = new StreamResult(bos); + marshaller.marshal(object, streamResult); + return new Message(bos.toByteArray(), messageProperties); + } + catch (XmlMappingException ex) { + throw new MessageConversionException("Could not marshal [" + object + "]", ex); + } + catch (IOException ex) { + throw new MessageConversionException("Could not marshal [" + object + "]", ex); + } + } + + /** + * Unmarshals the given {@link Message} into an object. + */ + public Object fromMessage(Message message) throws MessageConversionException { + try { + ByteArrayInputStream bis = new ByteArrayInputStream(message.getBody()); + StreamSource source = new StreamSource(bis); + return unmarshaller.unmarshal(source); + } + catch (IOException ex) { + throw new MessageConversionException("Could not access message content: " + message, ex); + } + catch (XmlMappingException ex) { + throw new MessageConversionException("Could not unmarshal message: " + message, ex); + } + } + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/support/converter/MessageConversionException.java b/spring-amqp-core/src/main/java/org/springframework/amqp/support/converter/MessageConversionException.java new file mode 100644 index 00000000..0402817e --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/support/converter/MessageConversionException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2010 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.amqp.support.converter; + +import org.springframework.amqp.AmqpException; + +/** + * @author Mark Fisher + */ +public class MessageConversionException extends AmqpException { + + public MessageConversionException(String message, Throwable cause) { + super(message, cause); + } + + public MessageConversionException(String message) { + super(message); + } + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/support/converter/MessageConverter.java b/spring-amqp-core/src/main/java/org/springframework/amqp/support/converter/MessageConverter.java new file mode 100644 index 00000000..978b8c6f --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/support/converter/MessageConverter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2010 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.amqp.support.converter; + +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; + +/** + * @author Mark Fisher + * @author Mark Pollack + */ +public interface MessageConverter { + + /** + * Convert a Java object to a Rabbit Message. + * @param object the object to convert + * @return the Rabbit Message + * @throws MessageConversionException in case of conversion failure + */ + Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException; + + /** + * Convert from a Rabbit GetResponse to a Java object. + * @param message the message to convert + * @return the converted Java object + * @throws MessageConversionException in case of conversion failure + */ + Object fromMessage(Message message) throws MessageConversionException; + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/support/converter/SimpleMessageConverter.java b/spring-amqp-core/src/main/java/org/springframework/amqp/support/converter/SimpleMessageConverter.java new file mode 100644 index 00000000..6127cb2e --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/support/converter/SimpleMessageConverter.java @@ -0,0 +1,113 @@ +/* + * Copyright 2002-2010 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.amqp.support.converter; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; + +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.amqp.utils.SerializationUtils; + +/** + * Implementation of {@link MessageConverter} that can work with Strings, Serializable instances, + * or byte arrays. The {@link #toMessage(Object)} method simply checks the type of the provided + * instance while the {@link #fromMessage(Message)} method relies upon the + * {@link DefaultMessageProperties#getContentType() content-type} of the provided Message. + * + * @author Mark Fisher + * @author Oleg Zhurakousky + */ +public class SimpleMessageConverter implements MessageConverter { + + public static final String DEFAULT_CHARSET = "UTF-8"; + + + private volatile String defaultCharset = DEFAULT_CHARSET; + + + /** + * Specify the default charset to use when converting to or from text-based + * Message body content. If not specified, the charset will be "UTF-8". + */ + public void setDefaultCharset(String defaultCharset) { + this.defaultCharset = (defaultCharset != null) ? defaultCharset : DEFAULT_CHARSET; + } + + /** + * Converts from a Rabbit Message to an Object. + */ + public Object fromMessage(Message message) throws MessageConversionException { + Object content = null; + MessageProperties properties = message.getMessageProperties(); + if (properties != null) { + String contentType = properties.getContentType(); + if (contentType != null && contentType.startsWith("text")) { + String encoding = properties.getContentEncoding(); + if (encoding == null) { + encoding = this.defaultCharset; + } + try { + content = new String(message.getBody(), encoding); + } + catch (UnsupportedEncodingException e) { + throw new MessageConversionException( + "failed to convert text-based Message content", e); + } + } + else if (contentType != null && + contentType.equals(MessageProperties.CONTENT_TYPE_SERIALIZED_OBJECT)) { + content = SerializationUtils.deserialize(message.getBody()); + } + } + if (content == null) { + content = message.getBody(); + } + return content; + } + + /** + * Creates a Rabbit Mesasge from the provided Object. + */ + public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException { + byte[] bytes = null; + if (object instanceof byte[]) { + bytes = (byte[]) object; + messageProperties.setContentType(MessageProperties.CONTENT_TYPE_BYTES); + } + else if (object instanceof String) { + try { + bytes = ((String) object).getBytes(this.defaultCharset); + } + catch (UnsupportedEncodingException e) { + throw new MessageConversionException( + "failed to convert Message content", e); + } + messageProperties.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN); + messageProperties.setContentEncoding(this.defaultCharset); + } + else if (object instanceof Serializable) { + bytes = SerializationUtils.serialize((Serializable) object); + messageProperties.setContentType(MessageProperties.CONTENT_TYPE_SERIALIZED_OBJECT); + } + if (bytes != null) { + messageProperties.setContentLength(bytes.length); + } + return new Message(bytes, messageProperties); + } + +} diff --git a/spring-amqp-core/src/main/java/org/springframework/amqp/utils/SerializationUtils.java b/spring-amqp-core/src/main/java/org/springframework/amqp/utils/SerializationUtils.java new file mode 100644 index 00000000..10bb210f --- /dev/null +++ b/spring-amqp-core/src/main/java/org/springframework/amqp/utils/SerializationUtils.java @@ -0,0 +1,71 @@ +/* + * Copyright 2006-2010 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.amqp.utils; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** + * Static utility to help with serialization. + * + * @author Dave Syer + */ +public class SerializationUtils { + + /** + * Serialize the object provided. + * + * @param object the object to serialize + * @return an array of bytes representing the object in a portable fashion + */ + public static byte[] serialize(Object object) { + if (object == null) { + return null; + } + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + try { + new ObjectOutputStream(stream).writeObject(object); + } + catch (IOException e) { + throw new IllegalArgumentException("Could not serialize object of type: " + object.getClass(), e); + } + return stream.toByteArray(); + } + + /** + * @param bytes a serialized object created + * @return the result of deserializing the bytes + */ + public static Object deserialize(byte[] bytes) { + if (bytes == null) { + return null; + } + try { + return new ObjectInputStream(new ByteArrayInputStream(bytes)).readObject(); + } + catch (IOException e) { + throw new IllegalArgumentException("Could not deserialize object", e); + } + catch (ClassNotFoundException e) { + throw new IllegalStateException("Could not deserialize object type", e); + } + } + +} diff --git a/spring-amqp-core/src/test/java/README.txt b/spring-amqp-core/src/test/java/README.txt new file mode 100644 index 00000000..37c1a980 --- /dev/null +++ b/spring-amqp-core/src/test/java/README.txt @@ -0,0 +1,8 @@ +Many of the "integration" tests here require a running RabbitMQ server. + +If RabbitMQ is not installed on your system, refer to the instructions for your +particular OS here: http://www.rabbitmq.com/install.html + +Start the server. For example, and a *nix-based system where RabbitMQ has been +installed within /opt/rabbitmq, issue the following command: +> sudo /opt/rabbitmq/sbin/rabbitmq-server start diff --git a/spring-amqp-core/src/test/java/org/springframework/amqp/core/AddressTests.java b/spring-amqp-core/src/test/java/org/springframework/amqp/core/AddressTests.java new file mode 100644 index 00000000..6f7f786c --- /dev/null +++ b/spring-amqp-core/src/test/java/org/springframework/amqp/core/AddressTests.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2010 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.amqp.core; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import junit.framework.Assert; + +import org.junit.Test; + +/** + * @author Mark Pollack + */ +public class AddressTests { + + @Test + public void parse() { + Address a = new Address(ExchangeType.direct, "my-exchange", "routing-key"); + String replyToUri = "direct://my-exchange/routing-key"; + Assert.assertEquals(replyToUri, a.toString()); + + Address b = Address.parse(replyToUri); + assertEquals(b.getExchangeType(), ExchangeType.direct); + assertEquals(b.getExchangeName(), "my-exchange"); + assertEquals(b.getRoutingKey(), "routing-key"); + assertTrue(b.isStructured()); + + b = Address.parse("my-exchange/routing-key"); + assertFalse(b.isStructured()); + assertEquals("my-exchange/routing-key", b.toString()); + } + +} diff --git a/spring-amqp-core/src/test/java/org/springframework/amqp/core/MessageTests.java b/spring-amqp-core/src/test/java/org/springframework/amqp/core/MessageTests.java new file mode 100644 index 00000000..48d2f076 --- /dev/null +++ b/spring-amqp-core/src/test/java/org/springframework/amqp/core/MessageTests.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2010 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.amqp.core; + +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; + +/** + * @author Mark Fisher + */ +public class MessageTests { + + @Test + public void toStringForEmptyMessage() { + Message message = new Message(new byte[0], new TestMessageProperties()); + assertNotNull(message.toString()); + } + +} diff --git a/spring-amqp-core/src/test/java/org/springframework/amqp/core/TestMessageProperties.java b/spring-amqp-core/src/test/java/org/springframework/amqp/core/TestMessageProperties.java new file mode 100644 index 00000000..3ef33e06 --- /dev/null +++ b/spring-amqp-core/src/test/java/org/springframework/amqp/core/TestMessageProperties.java @@ -0,0 +1,237 @@ +/* + * Copyright 2002-2010 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.amqp.core; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Mark Fisher + */ +public class TestMessageProperties implements MessageProperties { + + private volatile String appId; + + private volatile String clusterId; + + private volatile String contentEncoding; + + private volatile long contentLength; + + private volatile String contentType = MessageProperties.CONTENT_TYPE_BYTES; + + private volatile byte[] correlationId; + + private volatile MessageDeliveryMode deliveryMode = MessageDeliveryMode.PERSISTENT; + + private volatile long deliveryTag; + + private volatile String expiration; + + private volatile Map headers = new HashMap(); + + private volatile Integer messageCount; + + private volatile String messageId; + + private volatile Integer priority; + + private volatile String receivedExchange; + + private volatile String receivedRoutingKey; + + private volatile Address replyTo; + + private volatile Date timestamp; + + private volatile String type; + + private volatile String userId; + + private volatile Boolean redelivered; + + + public String getAppId() { + return this.appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getClusterId() { + return this.clusterId; + } + + public void setClusterId(String clusterId) { + this.clusterId = clusterId; + } + + public String getContentEncoding() { + return this.contentEncoding; + } + + public void setContentEncoding(String contentEncoding) { + this.contentEncoding = contentEncoding; + } + + public long getContentLength() { + return this.contentLength; + } + + public void setContentLength(long contentLength) { + this.contentLength = contentLength; + } + + public String getContentType() { + return this.contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public byte[] getCorrelationId() { + return this.correlationId; + } + + public void setCorrelationId(byte[] correlationId) { + this.correlationId = correlationId; + } + + public MessageDeliveryMode getDeliveryMode() { + return this.deliveryMode; + } + + public void setDeliveryMode(MessageDeliveryMode deliveryMode) { + this.deliveryMode = deliveryMode; + } + + public long getDeliveryTag() { + return this.deliveryTag; + } + + public void setDeliveryTag(long deliveryTag) { + this.deliveryTag = deliveryTag; + } + + public String getExpiration() { + return this.expiration; + } + + public void setExpiration(String expiration) { + this.expiration = expiration; + } + + public Map getHeaders() { + return this.headers; + } + + public void setHeaders(Map headers) { + this.headers = headers; + } + + public Integer getMessageCount() { + return this.messageCount; + } + + public void setMessageCount(Integer messageCount) { + this.messageCount = messageCount; + } + + public String getMessageId() { + return this.messageId; + } + + public void setMessageId(String messageId) { + this.messageId = messageId; + } + + public Integer getPriority() { + return this.priority; + } + + public void setPriority(Integer priority) { + this.priority = priority; + } + + public String getReceivedExchange() { + return this.receivedExchange; + } + + public void setReceivedExchange(String receivedExchange) { + this.receivedExchange = receivedExchange; + } + + public String getReceivedRoutingKey() { + return this.receivedRoutingKey; + } + + public void setReceivedRoutingKey(String receivedRoutingKey) { + this.receivedRoutingKey = receivedRoutingKey; + } + + public Address getReplyTo() { + return this.replyTo; + } + + public void setReplyTo(Address replyTo) { + this.replyTo = replyTo; + } + + public Date getTimestamp() { + return this.timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + + public String getType() { + return this.type; + } + + public void setType(String type) { + this.type = type; + } + + public String getUserId() { + return this.userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public Boolean isRedelivered() { + return this.redelivered; + } + + public void setRedelivered(Boolean redelivered) { + this.redelivered = redelivered; + } + + public void setHeader(String key, Object value) { + this.headers.put(key, value); + } + + public void setDefaultCharset(String charSet) { + // TODO: where would this be used? + } + +} diff --git a/spring-amqp-core/src/test/java/org/springframework/amqp/support/converter/JsonMessageConverterTests.java b/spring-amqp-core/src/test/java/org/springframework/amqp/support/converter/JsonMessageConverterTests.java new file mode 100644 index 00000000..c48aac43 --- /dev/null +++ b/spring-amqp-core/src/test/java/org/springframework/amqp/support/converter/JsonMessageConverterTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2010 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.amqp.support.converter; + +import static org.junit.Assert.assertEquals; + +import java.math.BigDecimal; +import java.util.Hashtable; + +import org.junit.Test; + +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.TestMessageProperties; + +/** + * @author Mark Pollack + */ +public class JsonMessageConverterTests { + + @Test + public void simpleTrade() { + SimpleTrade trade = new SimpleTrade(); + trade.setAccountName("Acct1"); + trade.setBuyRequest(true); + trade.setOrderType("Market"); + trade.setPrice(new BigDecimal(103.30)); + trade.setQuantity(100); + trade.setRequestId("R123"); + trade.setTicker("VMW"); + trade.setUserName("Joe Trader"); + JsonMessageConverter converter = new JsonMessageConverter(); + Message message = converter.toMessage(trade, new TestMessageProperties()); + String classIdFieldName = converter.getClassMapper().getClassIdFieldName(); + Object classIdHeaderObject = message.getMessageProperties().getHeaders().get(classIdFieldName); + assertEquals(String.class, classIdHeaderObject.getClass()); + SimpleTrade marshalledTrade = (SimpleTrade) converter.fromMessage(message); + assertEquals(trade, marshalledTrade); + } + + @Test + @SuppressWarnings("unchecked") + public void hashtable() { + Hashtable hashtable = new Hashtable(); + hashtable.put("TICKER", "VMW"); + hashtable.put("PRICE", "103.2"); + JsonMessageConverter converter = new JsonMessageConverter(); + Message message = converter.toMessage(hashtable, new TestMessageProperties()); + Hashtable marhsalledHashtable = (Hashtable) converter.fromMessage(message); + assertEquals("VMW", marhsalledHashtable.get("TICKER")); + assertEquals("103.2", marhsalledHashtable.get("PRICE")); + } + +} diff --git a/spring-amqp-core/src/test/java/org/springframework/amqp/support/converter/MarshallingMessageConverterTests.java b/spring-amqp-core/src/test/java/org/springframework/amqp/support/converter/MarshallingMessageConverterTests.java new file mode 100644 index 00000000..f1bcc6be --- /dev/null +++ b/spring-amqp-core/src/test/java/org/springframework/amqp/support/converter/MarshallingMessageConverterTests.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2010 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.amqp.support.converter; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; + +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.junit.Test; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.TestMessageProperties; +import org.springframework.oxm.Marshaller; +import org.springframework.oxm.Unmarshaller; +import org.springframework.oxm.XmlMappingException; + +/** + * @author Mark Fisher + */ +public class MarshallingMessageConverterTests { + + @Test + public void marshal() throws Exception { + TestMarshaller marshaller = new TestMarshaller(); + MarshallingMessageConverter converter = new MarshallingMessageConverter(marshaller); + converter.afterPropertiesSet(); + Message message = converter.toMessage("marshal test", new TestMessageProperties()); + String response = new String(message.getBody(), "UTF-8"); + assertEquals("MARSHAL TEST", response); + } + + @Test + public void unmarshal() { + TestMarshaller marshaller = new TestMarshaller(); + MarshallingMessageConverter converter = new MarshallingMessageConverter(marshaller); + converter.afterPropertiesSet(); + Message message = new Message("UNMARSHAL TEST".getBytes(), new TestMessageProperties()); + Object result = converter.fromMessage(message); + assertEquals("unmarshal test", (String) result); + } + + + private static class TestMarshaller implements Marshaller, Unmarshaller { + + public boolean supports(Class clazz) { + return true; + } + + public void marshal(Object graph, Result result) throws IOException, XmlMappingException { + String response = ((String) graph).toUpperCase(); + ((StreamResult) result).getOutputStream().write(response.getBytes()); + } + + public Object unmarshal(Source source) throws IOException, XmlMappingException { + byte[] buffer = new byte["UNMARSHAL TEST".length()]; + ((StreamSource) source).getInputStream().read(buffer); + return new String(buffer, "UTF-8").toLowerCase(); + } + } + +} diff --git a/spring-amqp-core/src/test/java/org/springframework/amqp/support/converter/SimpleMessageConverterTests.java b/spring-amqp-core/src/test/java/org/springframework/amqp/support/converter/SimpleMessageConverterTests.java new file mode 100644 index 00000000..dee7bfab --- /dev/null +++ b/spring-amqp-core/src/test/java/org/springframework/amqp/support/converter/SimpleMessageConverterTests.java @@ -0,0 +1,146 @@ +/* + * Copyright 2002-2010 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.amqp.support.converter; + +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import org.junit.Test; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.amqp.core.TestMessageProperties; +import org.springframework.util.Assert; + +/** + * @author Mark Fisher + */ +public class SimpleMessageConverterTests { + + @Test + public void bytesAsDefaultMessageBodyType() throws Exception { + SimpleMessageConverter converter = new SimpleMessageConverter(); + Message message = new Message("test".getBytes(), new TestMessageProperties()); + Object result = converter.fromMessage(message); + assertEquals(byte[].class, result.getClass()); + assertEquals("test", new String((byte[]) result, "UTF-8")); + } + + @Test + public void messageToString() { + SimpleMessageConverter converter = new SimpleMessageConverter(); + Message message = new Message("test".getBytes(), new TestMessageProperties()); + message.getMessageProperties().setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN); + Object result = converter.fromMessage(message); + assertEquals(String.class, result.getClass()); + assertEquals("test", result); + } + + @Test + public void messageToBytes() { + SimpleMessageConverter converter = new SimpleMessageConverter(); + Message message = new Message(new byte[] { 1, 2, 3 }, new TestMessageProperties()); + message.getMessageProperties().setContentType(MessageProperties.CONTENT_TYPE_BYTES); + Object result = converter.fromMessage(message); + assertEquals(byte[].class, result.getClass()); + byte[] resultBytes = (byte[]) result; + assertEquals(3, resultBytes.length); + assertEquals(1, resultBytes[0]); + assertEquals(2, resultBytes[1]); + assertEquals(3, resultBytes[2]); + } + + @Test + public void messageToSerializedObject() throws Exception { + SimpleMessageConverter converter = new SimpleMessageConverter(); + MessageProperties properties = new TestMessageProperties(); + properties.setContentType(MessageProperties.CONTENT_TYPE_SERIALIZED_OBJECT); + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + ObjectOutputStream objectStream = new ObjectOutputStream(byteStream); + TestBean testBean = new TestBean("foo"); + objectStream.writeObject(testBean); + objectStream.flush(); + objectStream.close(); + byte[] bytes = byteStream.toByteArray(); + Message message = new Message(bytes, properties); + Object result = converter.fromMessage(message); + assertEquals(TestBean.class, result.getClass()); + assertEquals(testBean, result); + } + + @Test + public void stringToMessage() throws Exception { + SimpleMessageConverter converter = new SimpleMessageConverter(); + Message message = converter.toMessage("test", new TestMessageProperties()); + String contentType = message.getMessageProperties().getContentType(); + String content = new String(message.getBody(), + message.getMessageProperties().getContentEncoding()); + assertEquals("text/plain", contentType); + assertEquals("test", content); + } + + @Test + public void bytesToMessage() throws Exception { + SimpleMessageConverter converter = new SimpleMessageConverter(); + Message message = converter.toMessage(new byte[] { 1, 2, 3 }, new TestMessageProperties()); + String contentType = message.getMessageProperties().getContentType(); + byte[] body = message.getBody(); + assertEquals("application/octet-stream", contentType); + assertEquals(3, body.length); + assertEquals(1, body[0]); + assertEquals(2, body[1]); + assertEquals(3, body[2]); + } + + @Test + public void serializedObjectToMessage() throws Exception { + SimpleMessageConverter converter = new SimpleMessageConverter(); + TestBean testBean = new TestBean("foo"); + Message message = converter.toMessage(testBean, new TestMessageProperties()); + String contentType = message.getMessageProperties().getContentType(); + byte[] body = message.getBody(); + assertEquals("application/x-java-serialized-object", contentType); + ByteArrayInputStream bais = new ByteArrayInputStream(body); + Object deserializedObject = new ObjectInputStream(bais).readObject(); + assertEquals(testBean, deserializedObject); + } + + + @SuppressWarnings("serial") + private static class TestBean implements Serializable { + + private final String text; + + TestBean(String text) { + Assert.notNull(text, "text must not be null"); + this.text = text; + } + + public boolean equals(Object other) { + return (other instanceof TestBean && this.text.equals(((TestBean) other).text)); + } + + public int hashCode() { + return this.text.hashCode(); + } + } + +} diff --git a/spring-amqp-core/src/test/java/org/springframework/amqp/support/converter/SimpleTrade.java b/spring-amqp-core/src/test/java/org/springframework/amqp/support/converter/SimpleTrade.java new file mode 100644 index 00000000..af1de286 --- /dev/null +++ b/spring-amqp-core/src/test/java/org/springframework/amqp/support/converter/SimpleTrade.java @@ -0,0 +1,153 @@ +package org.springframework.amqp.support.converter; + +import java.math.BigDecimal; + +public class SimpleTrade { + + private String ticker; + + private long quantity; + + private BigDecimal price; + + private String orderType; + + private String accountName; + + private boolean buyRequest; + + private String userName; + + private String requestId; + + public String getTicker() { + return ticker; + } + + public void setTicker(String ticker) { + this.ticker = ticker; + } + + public long getQuantity() { + return quantity; + } + + public void setQuantity(long quantity) { + this.quantity = quantity; + } + + public BigDecimal getPrice() { + return price; + } + + public void setPrice(BigDecimal price) { + this.price = price; + } + + public String getOrderType() { + return orderType; + } + + public void setOrderType(String orderType) { + this.orderType = orderType; + } + + public String getAccountName() { + return accountName; + } + + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + public boolean isBuyRequest() { + return buyRequest; + } + + public void setBuyRequest(boolean buyRequest) { + this.buyRequest = buyRequest; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((accountName == null) ? 0 : accountName.hashCode()); + result = prime * result + (buyRequest ? 1231 : 1237); + result = prime * result + + ((orderType == null) ? 0 : orderType.hashCode()); + result = prime * result + ((price == null) ? 0 : price.hashCode()); + result = prime * result + (int) (quantity ^ (quantity >>> 32)); + result = prime * result + + ((requestId == null) ? 0 : requestId.hashCode()); + result = prime * result + ((ticker == null) ? 0 : ticker.hashCode()); + result = prime * result + + ((userName == null) ? 0 : userName.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SimpleTrade other = (SimpleTrade) obj; + if (accountName == null) { + if (other.accountName != null) + return false; + } else if (!accountName.equals(other.accountName)) + return false; + if (buyRequest != other.buyRequest) + return false; + if (orderType == null) { + if (other.orderType != null) + return false; + } else if (!orderType.equals(other.orderType)) + return false; + if (price == null) { + if (other.price != null) + return false; + } else if (!price.equals(other.price)) + return false; + if (quantity != other.quantity) + return false; + if (requestId == null) { + if (other.requestId != null) + return false; + } else if (!requestId.equals(other.requestId)) + return false; + if (ticker == null) { + if (other.ticker != null) + return false; + } else if (!ticker.equals(other.ticker)) + return false; + if (userName == null) { + if (other.userName != null) + return false; + } else if (!userName.equals(other.userName)) + return false; + return true; + } + + +} diff --git a/spring-amqp-core/src/test/resources/log4j.properties b/spring-amqp-core/src/test/resources/log4j.properties new file mode 100644 index 00000000..6ddfb097 --- /dev/null +++ b/spring-amqp-core/src/test/resources/log4j.properties @@ -0,0 +1,9 @@ +log4j.rootCategory=INFO, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n + +log4j.category.org.springframework.amqp=DEBUG +log4j.category.org.springframework.beans.factory=INFO + diff --git a/spring-amqp-core/template.mf b/spring-amqp-core/template.mf new file mode 100644 index 00000000..624706b7 --- /dev/null +++ b/spring-amqp-core/template.mf @@ -0,0 +1,10 @@ +Bundle-SymbolicName: org.springframework.amqp +Bundle-Name: Spring AMQP Core +Bundle-Vendor: SpringSource +Bundle-ManifestVersion: 2 +Import-Template: + org.springframework.*;version="[3.0.3, 4.0.0)", + org.apache.commons.logging;version="[1.1.1, 2.0.0)", + org.codehaus.jackson.*;version="[1.4.3, 2.0.0)";resolution:=optional, + org.w3c.dom.*;version="0", + javax.xml.*;version="0" diff --git a/spring-amqp-parent/.project b/spring-amqp-parent/.project new file mode 100644 index 00000000..60744225 --- /dev/null +++ b/spring-amqp-parent/.project @@ -0,0 +1,17 @@ + + + spring-amqp-parent + + + + + + org.maven.ide.eclipse.maven2Builder + + + + + + org.maven.ide.eclipse.maven2Nature + + diff --git a/spring-amqp-parent/.settings/org.maven.ide.eclipse.prefs b/spring-amqp-parent/.settings/org.maven.ide.eclipse.prefs new file mode 100644 index 00000000..7ccc9920 --- /dev/null +++ b/spring-amqp-parent/.settings/org.maven.ide.eclipse.prefs @@ -0,0 +1,9 @@ +#Tue Jun 29 21:59:00 EDT 2010 +activeProfiles= +eclipse.preferences.version=1 +fullBuildGoals=process-test-resources +includeModules=false +resolveWorkspaceProjects=true +resourceFilterGoals=process-resources resources\:testResources +skipCompilerPlugin=true +version=1 diff --git a/spring-amqp-parent/pom.xml b/spring-amqp-parent/pom.xml new file mode 100644 index 00000000..cf3c72d9 --- /dev/null +++ b/spring-amqp-parent/pom.xml @@ -0,0 +1,382 @@ + + + 4.0.0 + org.springframework.amqp + spring-amqp-parent + Spring AMQP Parent + http://www.springsource.org/spring-amqp + 1.0.0.BUILD-SNAPSHOT + pom + + UTF-8 + + 2.2 + 1.4 + 4.7 + 1.2.15 + 1.8.4 + 1.5.10 + 1.4.3 + 1.5.3 + 1.7.2 + 3.0.3.RELEASE + + + + strict + + false + + + + fast + + true + true + + + + staging + + + staging + file:///${java.io.tmpdir}/target/site + + + staging + file:///${java.io.tmpdir}/target/repository + + + staging + file:///${java.io.tmpdir}/target/snapshotRepository + + + + + + + http://static.springframework.org/spring-amqp/site/downloads/releases.html + + local + file://${java.io.tmpdir}/spring-amqp-repo + + + spring-snapshot + Spring Snapshot Repository + s3://maven.springframework.org/snapshot + + + static.springframework.org + scp://static.springframework.org/var/www/domains/springframework.org/static/htdocs/spring-amqp/sites/${project.version} + + + + + + + com.rabbitmq + amqp-client + ${com.rabbitmq.version} + + + org.springframework.amqp + spring-amqp-core + ${project.version} + + + org.springframework.amqp + spring-rabbit + ${project.version} + + + org.springframework.amqp + spring-otp + ${project.version} + + + + + org.springframework + spring-aop + ${org.springframework.version} + + + org.springframework + spring-beans + ${org.springframework.version} + + + org.springframework + spring-oxm + ${org.springframework.version} + + + org.springframework + spring-tx + ${org.springframework.version} + + + org.springframework + spring-test + ${org.springframework.version} + + + + + + commons-io + commons-io + ${commons-io.version} + + + + + org.slf4j + slf4j-api + ${org.slf4j.version} + + + org.slf4j + jcl-over-slf4j + ${org.slf4j.version} + runtime + + + org.slf4j + slf4j-log4j12 + ${org.slf4j.version} + runtime + + + log4j + log4j + ${log4j.version} + + + javax.mail + mail + + + javax.jms + jms + + + com.sun.jdmk + jmxtools + + + com.sun.jmx + jmxri + + + runtime + + + + javax.annotation + jsr250-api + 1.0 + true + + + + cglib + cglib-nodep + ${cglib.version} + true + + + + org.mockito + mockito-all + ${org.mockito.version} + test + + + + junit + junit + ${junit.version} + + + + + org.codehaus.jackson + jackson-core-asl + ${org.codehaus.jackson.version} + + + org.codehaus.jackson + jackson-mapper-asl + ${org.codehaus.jackson.version} + + + + org.erlang.otp + jinterface + ${org.erlang.otp.version} + + + + + + + log4j + log4j + ${log4j.version} + test + + + + + + + org.springframework.build.aws + org.springframework.build.aws.maven + 3.0.0.RELEASE + + + + + ${project.basedir}/src/main/java + + **/* + + + **/*.java + + + + ${project.basedir}/src/main/resources + + **/* + + + + + + ${project.basedir}/src/test/java + + **/* + + + **/*.java + + + + ${project.basedir}/src/test/resources + + **/* + + + **/*.java + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.5 + 1.5 + -Xlint:all + true + false + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + **/*Tests.java + + + **/Abstract*.java + + junit:junit + + + + maven-source-plugin + + + attach-sources + + jar + + + + + + + + + com.springsource.bundlor + com.springsource.bundlor.maven + 1.0.0.RELEASE + + true + + + + bundlor + + bundlor + + + + + + + + + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 2.1 + + false + + + + + + + + repository.plugin.springsource.release + SpringSource Maven Repository + http://repository.springsource.com/maven/bundles/release + + + + + + repository.springframework.maven.release + Spring Framework Maven Release Repository + http://maven.springframework.org/release + + + diff --git a/spring-amqp-samples/helloworld/.classpath b/spring-amqp-samples/helloworld/.classpath new file mode 100644 index 00000000..575d9eaf --- /dev/null +++ b/spring-amqp-samples/helloworld/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/spring-amqp-samples/helloworld/.project b/spring-amqp-samples/helloworld/.project new file mode 100644 index 00000000..018645dc --- /dev/null +++ b/spring-amqp-samples/helloworld/.project @@ -0,0 +1,29 @@ + + + spring-rabbit-helloworld + a simple Hello World example + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.springframework.ide.eclipse.core.springbuilder + + + + + org.maven.ide.eclipse.maven2Builder + + + + + + org.springframework.ide.eclipse.core.springnature + org.eclipse.jdt.core.javanature + org.maven.ide.eclipse.maven2Nature + + diff --git a/spring-amqp-samples/helloworld/.springBeans b/spring-amqp-samples/helloworld/.springBeans new file mode 100644 index 00000000..0c014a97 --- /dev/null +++ b/spring-amqp-samples/helloworld/.springBeans @@ -0,0 +1,20 @@ + + + 1 + + + + + + + + + + + true + false + + + + + diff --git a/spring-amqp-samples/helloworld/pom.xml b/spring-amqp-samples/helloworld/pom.xml new file mode 100644 index 00000000..3572dfaa --- /dev/null +++ b/spring-amqp-samples/helloworld/pom.xml @@ -0,0 +1,174 @@ + + + 4.0.0 + org.springframework.samples.spring + spring-rabbit-helloworld + 1.0.0.BUILD-SNAPSHOT + jar + Spring AMQP Hello World + http://www.springframework.org + + + + + true + 3.0.3.RELEASE + 1.0.0.BUILD-SNAPSHOT + + + + strict + + false + + + + + + + + org.springframework.amqp + spring-amqp-core + ${spring.amqp.version} + + + org.springframework.amqp + spring-rabbit + ${spring.amqp.version} + + + org.springframework.amqp + spring-rabbit-admin + ${spring.amqp.version} + + + com.rabbitmq + amqp-client + 1.7.2 + + + + org.springframework + spring-test + ${spring.framework.version} + test + + + org.springframework + spring-context-support + ${spring.framework.version} + runtime + + + org.springframework + spring-aop + ${spring.framework.version} + + + org.springframework + spring-oxm + ${spring.framework.version} + + + aspectj + aspectjrt + 1.5.4 + + + aspectj + aspectjweaver + 1.5.4 + + + + + junit + junit + 4.7 + test + + + + log4j + log4j + 1.2.14 + runtime + + + + + + + + + com.springsource.bundlor + com.springsource.bundlor.maven + 1.0.0.M1B + + + maven-assembly-plugin + false + + + project + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.5 + 1.5 + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + **/*Tests.java + + + **/Abstract*.java + + + + + + + + + Codehaus + http://repository.codehaus.org/ + + false + + + + + http://www.springframework.org/download + + staging + file:///${user.dir}/target/staging/org.springframework.batch.archetype/${pom.artifactId} + + + spring-release + Spring Release Repository + file:///${user.dir}/target/staging/release + + + spring-snapshot + Spring Snapshot Repository + file:///${user.dir}/target/staging/snapshot + + + diff --git a/spring-amqp-samples/helloworld/src/main/java/org/springframework/amqp/helloworld/Consumer.java b/spring-amqp-samples/helloworld/src/main/java/org/springframework/amqp/helloworld/Consumer.java new file mode 100644 index 00000000..078823b8 --- /dev/null +++ b/spring-amqp-samples/helloworld/src/main/java/org/springframework/amqp/helloworld/Consumer.java @@ -0,0 +1,19 @@ +package org.springframework.amqp.helloworld; + +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +public class Consumer { + + public static void main(String[] args) { + //ApplicationContext ctx = new AnnotationConfigApplicationContext(ConsumerConfiguration.class); + + + ApplicationContext ctx = new AnnotationConfigApplicationContext(RabbitConfiguration.class); + RabbitTemplate rabbitTemplate = (RabbitTemplate) ctx.getBean(RabbitTemplate.class); + + System.out.println("Received " + rabbitTemplate.receiveAndConvert()); + + } +} diff --git a/spring-amqp-samples/helloworld/src/main/java/org/springframework/amqp/helloworld/ConsumerConfiguration.java b/spring-amqp-samples/helloworld/src/main/java/org/springframework/amqp/helloworld/ConsumerConfiguration.java new file mode 100644 index 00000000..65dfa948 --- /dev/null +++ b/spring-amqp-samples/helloworld/src/main/java/org/springframework/amqp/helloworld/ConsumerConfiguration.java @@ -0,0 +1,20 @@ +package org.springframework.amqp.helloworld; + +import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; +import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ConsumerConfiguration extends RabbitConfiguration { + + @Bean + public SimpleMessageListenerContainer listenerContainer() { + SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); + container.setConnectionFactory(connectionFactory()); + container.setQueueName(helloWorldQueue.getName()); + container.setMessageListener(new MessageListenerAdapter(new HelloWorldHandler())); + return container; + } + +} diff --git a/spring-amqp-samples/helloworld/src/main/java/org/springframework/amqp/helloworld/HelloWorldHandler.java b/spring-amqp-samples/helloworld/src/main/java/org/springframework/amqp/helloworld/HelloWorldHandler.java new file mode 100644 index 00000000..8ed97448 --- /dev/null +++ b/spring-amqp-samples/helloworld/src/main/java/org/springframework/amqp/helloworld/HelloWorldHandler.java @@ -0,0 +1,8 @@ +package org.springframework.amqp.helloworld; + +public class HelloWorldHandler { + + public void handleMessage(String text) { + System.out.println("Received: " + text); + } +} diff --git a/spring-amqp-samples/helloworld/src/main/java/org/springframework/amqp/helloworld/Producer.java b/spring-amqp-samples/helloworld/src/main/java/org/springframework/amqp/helloworld/Producer.java new file mode 100644 index 00000000..2068d06d --- /dev/null +++ b/spring-amqp-samples/helloworld/src/main/java/org/springframework/amqp/helloworld/Producer.java @@ -0,0 +1,21 @@ +package org.springframework.amqp.helloworld; + +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +public class Producer { + + /** + * @param args + */ + public static void main(String[] args) { + ApplicationContext ctx = new AnnotationConfigApplicationContext(RabbitConfiguration.class); + + RabbitTemplate rabbitTemplate = (RabbitTemplate) ctx.getBean(RabbitTemplate.class); + rabbitTemplate.convertAndSend("Hello World"); + System.out.println("Sent Hello World."); + + } + +} diff --git a/spring-amqp-samples/helloworld/src/main/java/org/springframework/amqp/helloworld/RabbitConfiguration.java b/spring-amqp-samples/helloworld/src/main/java/org/springframework/amqp/helloworld/RabbitConfiguration.java new file mode 100644 index 00000000..1db595f3 --- /dev/null +++ b/spring-amqp-samples/helloworld/src/main/java/org/springframework/amqp/helloworld/RabbitConfiguration.java @@ -0,0 +1,89 @@ +package org.springframework.amqp.helloworld; + +import javax.annotation.PostConstruct; + +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.rabbit.admin.config.AbstractRabbitConfiguration; +import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitTemplate; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RabbitConfiguration extends AbstractRabbitConfiguration { + + protected Queue helloWorldQueue = new Queue("hello.world.queue"); + + @Bean + public ConnectionFactory connectionFactory() { + CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost"); + connectionFactory.setUsername("guest"); + connectionFactory.setPassword("guest"); + return connectionFactory; + } + + @Override + public RabbitTemplate rabbitTemplate() { + RabbitTemplate template = new RabbitTemplate(connectionFactory()); + //The routing key is set to the name of the queue by the broker for the default exchange. + template.setDefaultRoutingKey(helloWorldQueue.getName()); + //Where we will synchronously receive messages from + template.setDefaultReceiveQueue(helloWorldQueue); + return template; + } + + @PostConstruct + private void configureBroker() { + declare(helloWorldQueue); + } + + /* + @Bean + public Queue helloWorldQueue() + { + return declare(helloWorldQueue); + }*/ + + /* + //Each queue is bound to the default direct exchange + @Bean + public Binding binding() { + return declare(new Binding(helloWorldQueue(), defaultDirectExchange())); + }*/ + + /* + @Bean + public TopicExchange helloExchange() { + return declare(new TopicExchange("hello.world.exchange")); + }*/ + + /* + + public Queue declareUniqueQueue(String namePrefix) { + Queue queue = new Queue(namePrefix + "-" + UUID.randomUUID()); + rabbitAdminTemplate().declareQueue(queue); + return queue; + } + + // if the default exchange isn't configured to your liking.... + @Bean Binding declareP2PBinding(Queue queue, DirectExchange exchange) { + return declare(new Binding(queue, exchange, queue.getName())); + } + + @Bean Binding declarePubSubBinding(String queuePrefix, FanoutExchange exchange) { + return declare(new Binding(declareUniqueQueue(queuePrefix), exchange)); + } + + @Bean Binding declarePubSubBinding(UniqueQueue uniqueQueue, TopicExchange exchange) { + return declare(new Binding(uniqueQueue, exchange)); + } + + @Bean Binding declarePubSubBinding(String queuePrefix, TopicExchange exchange, String routingKey) { + return declare(new Binding(declareUniqueQueue(queuePrefix), exchange, routingKey)); + }*/ + + + +} diff --git a/spring-amqp-samples/helloworld/src/site/site.xml b/spring-amqp-samples/helloworld/src/site/site.xml new file mode 100644 index 00000000..4bba09c7 --- /dev/null +++ b/spring-amqp-samples/helloworld/src/site/site.xml @@ -0,0 +1,25 @@ + + + + + Spring Batch: ${project.name} + index.html + + + + org.springframework.maven.skins + maven-spring-skin + 1.0.5 + + + + + + + + +

+ + + + diff --git a/spring-amqp-samples/helloworld/src/test/resources/commons-logging.properties b/spring-amqp-samples/helloworld/src/test/resources/commons-logging.properties new file mode 100644 index 00000000..51cd942d --- /dev/null +++ b/spring-amqp-samples/helloworld/src/test/resources/commons-logging.properties @@ -0,0 +1,7 @@ +# Use Log4j +priority=1 +org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.LogFactoryImpl +org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger + +# Configuration file of the log +#log4j.configuration=file:log4j-rabbit-stocks.properties diff --git a/spring-amqp-samples/helloworld/src/test/resources/log4j.properties b/spring-amqp-samples/helloworld/src/test/resources/log4j.properties new file mode 100644 index 00000000..9687c70d --- /dev/null +++ b/spring-amqp-samples/helloworld/src/test/resources/log4j.properties @@ -0,0 +1,11 @@ +log4j.rootCategory=INFO, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + +#log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n +log4j.appender.stdout.layout.ConversionPattern=%-5p [%40.40c{4}]: %m%n + +log4j.category.org.springframework.amqp.rabbit=DEBUG +log4j.category.org.springframework.beans.factory=INFO + diff --git a/spring-amqp-samples/stocks/.classpath b/spring-amqp-samples/stocks/.classpath new file mode 100644 index 00000000..575d9eaf --- /dev/null +++ b/spring-amqp-samples/stocks/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/spring-amqp-samples/stocks/.project b/spring-amqp-samples/stocks/.project new file mode 100644 index 00000000..1632bb0f --- /dev/null +++ b/spring-amqp-samples/stocks/.project @@ -0,0 +1,29 @@ + + + spring-rabbit-stocks + sample of a basic trading app using RabbitMQ + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.springframework.ide.eclipse.core.springbuilder + + + + + org.maven.ide.eclipse.maven2Builder + + + + + + org.springframework.ide.eclipse.core.springnature + org.eclipse.jdt.core.javanature + org.maven.ide.eclipse.maven2Nature + + diff --git a/spring-amqp-samples/stocks/pom.xml b/spring-amqp-samples/stocks/pom.xml new file mode 100644 index 00000000..e782d245 --- /dev/null +++ b/spring-amqp-samples/stocks/pom.xml @@ -0,0 +1,185 @@ + + + 4.0.0 + org.springframework.samples.spring + spring-rabbit-stocks + 1.0.0.CI-SNAPSHOT + jar + Spring Rabbit Stocks + http://www.springframework.org + + + + + true + 3.0.3.RELEASE + 1.0.0.BUILD-SNAPSHOT + + + + strict + + false + + + + + + + + org.springframework.amqp + spring-amqp-core + ${spring.amqp.version} + + + org.springframework.amqp + spring-rabbit + ${spring.amqp.version} + + + org.springframework.amqp + spring-rabbit-admin + ${spring.amqp.version} + + + com.rabbitmq + amqp-client + 1.7.2 + + + + org.springframework + spring-test + ${spring.framework.version} + test + + + org.springframework + spring-context-support + ${spring.framework.version} + runtime + + + org.springframework + spring-aop + ${spring.framework.version} + + + org.springframework + spring-oxm + ${spring.framework.version} + + + cglib + cglib-nodep + 2.2 + + + aspectj + aspectjrt + 1.5.4 + + + aspectj + aspectjweaver + 1.5.4 + + + + + junit + junit + 4.7 + test + + + + log4j + log4j + 1.2.14 + runtime + + + + com.jgoodies + forms + 1.2.1 + + + + + + + + + com.springsource.bundlor + com.springsource.bundlor.maven + 1.0.0.M1B + + + maven-assembly-plugin + false + + + project + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.5 + 1.5 + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + **/*Tests.java + + + **/Abstract*.java + + + + + + + + + Codehaus + http://repository.codehaus.org/ + + false + + + + + http://www.springframework.org/download + + staging + file:///${user.dir}/target/staging/org.springframework.batch.archetype/${pom.artifactId} + + + spring-release + Spring Release Repository + file:///${user.dir}/target/staging/release + + + spring-snapshot + Spring Snapshot Repository + file:///${user.dir}/target/staging/snapshot + + + diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/config/AbstractStockAppRabbitConfiguration.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/config/AbstractStockAppRabbitConfiguration.java new file mode 100644 index 00000000..ab6fcd61 --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/config/AbstractStockAppRabbitConfiguration.java @@ -0,0 +1,98 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.config; + +import org.springframework.amqp.core.TopicExchange; +import org.springframework.amqp.rabbit.admin.config.AbstractRabbitConfiguration; +import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.support.converter.JsonMessageConverter; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Provides shared configuration between Client and Server. + *

The abstract method configureRabbitTemplate lets the Client and Server further customize + * the rabbit template to their specific needs. + * + * @author Mark Pollack + * @author Mark Fisher + */ +@Configuration +public abstract class AbstractStockAppRabbitConfiguration extends AbstractRabbitConfiguration { + + /** + * Shared topic exchange used for publishing any market data (e.g. stock quotes) + */ + protected static String MARKET_DATA_EXCHANGE_NAME = "app.stock.marketdata"; + + /** + * The server-side consumer's queue that provides point-to-point semantics for stock requests. + */ + protected static String STOCK_REQUEST_QUEUE_NAME = "app.stock.request"; + + /** + * Key that clients will use to send to the stock request queue via the default direct exchange. + */ + protected static String STOCK_REQUEST_ROUTING_KEY = STOCK_REQUEST_QUEUE_NAME; + + //protected static TopicExchange MARKET_DATA_EXCHANGE = new TopicExchange(MARKET_DATA_EXCHANGE_NAME); + + + protected abstract void configureRabbitTemplate(RabbitTemplate template); + + @Bean + public ConnectionFactory connectionFactory() { + //TODO make it possible to customize in subclasses. + CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost"); + connectionFactory.setUsername("guest"); + connectionFactory.setPassword("guest"); + connectionFactory.setChannelCacheSize(10); + return connectionFactory; + } + + @Bean + public RabbitTemplate rabbitTemplate() { + RabbitTemplate template = new RabbitTemplate(connectionFactory()); + template.setMessageConverter(jsonMessageConverter()); + configureRabbitTemplate(template); + return template; + } + + @Bean + public MessageConverter jsonMessageConverter() { + return new JsonMessageConverter(); + } + + +// @PostConstruct +// public void declareExchange() +// { +// declare(this.MARKET_DATA_EXCHANGE); +// } + + + @Bean + public TopicExchange marketDataExchange() { + return declare(new TopicExchange(MARKET_DATA_EXCHANGE_NAME)); + } + + + +} diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/config/RoutingKey.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/config/RoutingKey.java new file mode 100644 index 00000000..2623aa7a --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/config/RoutingKey.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.config; + +/** + * Enumerations for the RoutingKeys used in the application to allow for a more fluent API + * style when configuring the broker in code. + * + * @author Mark Pollack + */ +public enum RoutingKey { + +} diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/config/client/RabbitClientConfiguration.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/config/client/RabbitClientConfiguration.java new file mode 100644 index 00000000..d281f98b --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/config/client/RabbitClientConfiguration.java @@ -0,0 +1,136 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.config.client; + +import javax.annotation.PostConstruct; + +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.UniquelyNamedQueue; + +import static org.springframework.amqp.core.BindingBuilder.*; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; +import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter; +import org.springframework.amqp.rabbit.stocks.config.AbstractStockAppRabbitConfiguration; +import org.springframework.amqp.rabbit.stocks.gateway.RabbitStockServiceGateway; +import org.springframework.amqp.rabbit.stocks.gateway.StockServiceGateway; +import org.springframework.amqp.rabbit.stocks.handler.ClientHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Configures RabbitTemplate and creates the Trader queue and binding for the client. + * + * @author Mark Pollack + * @author Mark Fisher + */ +@Configuration +public class RabbitClientConfiguration extends AbstractStockAppRabbitConfiguration { + + @Value("${stocks.quote.pattern}") + private String marketDataRoutingKey; + + @Autowired + private ClientHandler clientHandler; + + + // Create the Queue definitions that write up the Message listener container + + //private Queue marketDataQueue = new UniquelyNamedQueue("mktdata"); + + //private Queue traderJoeQueue = new UniquelyNamedQueue("joe"); + + /** + * The client's template will by default send to the exchange defined + * in {@link AbstractRabbitConfiguration.rabbitTemplate()} + * with the routing key {@link AbstractStockAppRabbitConfiguration#STOCK_REQUEST_QUEUE_NAME} + *

+ * The default exchange will delivery to a queue whose name matches the routing key value. + */ + @Override + public void configureRabbitTemplate(RabbitTemplate rabbitTemplate) { + rabbitTemplate.setDefaultRoutingKey(STOCK_REQUEST_QUEUE_NAME); + } + + @Bean + public StockServiceGateway stockServiceGateway() { + RabbitStockServiceGateway gateway = new RabbitStockServiceGateway(); + gateway.setRabbitTemplate(rabbitTemplate()); + gateway.setDefaultReplyToQueue(traderJoeQueue()); + return gateway; + } + + @Bean + public SimpleMessageListenerContainer messageListenerContainer() { + SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory()); + container.setQueues(marketDataQueue(), traderJoeQueue()); + //container.setConcurrentConsumers(5); // note, now set to size of channel cache in CachingConnectionFactory by default + container.setMessageListener(messageListenerAdapter()); + return container; + + //container(using(connectionFactory()).listenToQueues(marketDataQueue(), traderJoeQueue()).withListener(messageListenerAdapter()). + } + + @Bean + public MessageListenerAdapter messageListenerAdapter() { + return new MessageListenerAdapter(clientHandler, jsonMessageConverter()); + } + + + // Broker Configuration + +// @PostConstruct +// public void declareClientBrokerConfiguration() { +// declare(marketDataQueue); +// declare(new Binding(marketDataQueue, MARKET_DATA_EXCHANGE, marketDataRoutingKey)); +// declare(traderJoeQueue); +// // no need to bind traderJoeQueue as it is automatically bound to the default direct exchanage, which is what we will use +// +// //add as many declare statements as needed like a script. +// } + + @Bean + public Queue marketDataQueue() { + return declareQueue(); + } + + /** + * Binds to the market data exchange. Interested in any stock quotes. + * @return + */ + @Bean + public Binding marketDataBinding() { + return declare(new Binding(marketDataQueue(), marketDataExchange(), marketDataRoutingKey)); + + // Using BindingBuilder + //return declareBinding(from(marketDataQueue()).to(marketDataExchange()).with(marketDataRoutingKey)); + } + + /** + * This queue does not need a binding, since it relies on the default exchange. + */ + @Bean + public Queue traderJoeQueue() { + return declareQueue(); + } + + +} diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/config/server/RabbitServerConfiguration.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/config/server/RabbitServerConfiguration.java new file mode 100644 index 00000000..34113496 --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/config/server/RabbitServerConfiguration.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.config.server; + +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.rabbit.stocks.config.AbstractStockAppRabbitConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Configures RabbitTemplate for the server. + * + * @author Mark Pollack + * @author Mark Fisher + */ +@Configuration +public class RabbitServerConfiguration extends AbstractStockAppRabbitConfiguration { + + /** + * The server's template will by default send to the topic exchange named + * {@link AbstractStockAppRabbitConfiguration#MARKET_DATA_EXCHANGE_NAME}. + */ + public void configureRabbitTemplate(RabbitTemplate rabbitTemplate) { + rabbitTemplate.setDefaultExchange(MARKET_DATA_EXCHANGE_NAME); + } + + /** + * We don't need to define any binding for the stock request queue, since it's relying + * on the default (no-name) direct exchange to which every queue is implicitly bound. + */ + @Bean + public Queue stockRequestQueue() { + return declare(new Queue(STOCK_REQUEST_QUEUE_NAME)); + } + + + +} diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/domain/Quote.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/domain/Quote.java new file mode 100644 index 00000000..96efabce --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/domain/Quote.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.domain; + +/** + * Domain object representing a stock quote. + * + * @author Mark Fisher + */ +public class Quote { + + private Stock stock; + private String price; + + public Quote() { + } + + public Quote(Stock stock, String price) { + this.stock = stock; + this.price = price; + } + + public Stock getStock() { + return this.stock; + } + + public void setStock(Stock stock) { + this.stock = stock; + } + + public String getPrice() { + return price; + } + + public void setPrice(String price) { + this.price = price; + } + +} diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/domain/Stock.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/domain/Stock.java new file mode 100644 index 00000000..ac7faac9 --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/domain/Stock.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.domain; + +/** + * @author Mark Fisher + */ +public class Stock { + + private String ticker; + + private StockExchange stockExchange; + + public String getTicker() { + return ticker; + } + + public void setTicker(String ticker) { + this.ticker = ticker; + } + + public StockExchange getStockExchange() { + return stockExchange; + } + + public void setStockExchange(StockExchange stockExchange) { + this.stockExchange = stockExchange; + } + +} diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/domain/StockExchange.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/domain/StockExchange.java new file mode 100644 index 00000000..d7bfc6e2 --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/domain/StockExchange.java @@ -0,0 +1,28 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.domain; + +/** + * Enumeration for Stock Exchanges. + * + * @author Mark Fisher + */ +public enum StockExchange { + + nyse, nasdaq; + +} diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/domain/TradeRequest.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/domain/TradeRequest.java new file mode 100644 index 00000000..7bac7e33 --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/domain/TradeRequest.java @@ -0,0 +1,109 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.domain; + +import java.math.BigDecimal; + +/** + * Simple trade request 'data' object. No functionality in this 'domain' class. + * @author Mark Pollack + * + */ +public class TradeRequest { + + private String ticker; + + private long quantity; + + private BigDecimal price; + + private String orderType; + + private String accountName; + + private boolean buyRequest; + + private String userName; + + private String requestId; + + public String getTicker() { + return ticker; + } + + public void setTicker(String ticker) { + this.ticker = ticker; + } + + public long getQuantity() { + return quantity; + } + + public void setQuantity(long quantity) { + this.quantity = quantity; + } + + public BigDecimal getPrice() { + return price; + } + + public void setPrice(BigDecimal price) { + this.price = price; + } + + public String getOrderType() { + return orderType; + } + + public void setOrderType(String orderType) { + this.orderType = orderType; + } + + public String getAccountName() { + return accountName; + } + + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + public boolean isBuyRequest() { + return buyRequest; + } + + public void setBuyRequest(boolean buyRequest) { + this.buyRequest = buyRequest; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + + +} diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/domain/TradeResponse.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/domain/TradeResponse.java new file mode 100644 index 00000000..8770a7fb --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/domain/TradeResponse.java @@ -0,0 +1,107 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.domain; + +import java.math.BigDecimal; + +/** + * Simple trade request 'data' object. No functionality in this 'domain' class. + * @author Mark Pollack + * + */ +public class TradeResponse { + + private String ticker; + + private long quantity; + + private BigDecimal price; + + private String orderType; + + private String confirmationNumber; + + private boolean error; + + private String errorMessage; + + public String getTicker() { + return ticker; + } + + public void setTicker(String ticker) { + this.ticker = ticker; + } + + public long getQuantity() { + return quantity; + } + + public void setQuantity(long quantity) { + this.quantity = quantity; + } + + public BigDecimal getPrice() { + return price; + } + + public void setPrice(BigDecimal price) { + this.price = price; + } + + public String getOrderType() { + return orderType; + } + + public void setOrderType(String orderType) { + this.orderType = orderType; + } + + public String getConfirmationNumber() { + return confirmationNumber; + } + + public void setConfirmationNumber(String confirmationNumber) { + this.confirmationNumber = confirmationNumber; + } + + public boolean isError() { + return error; + } + + public void setError(boolean error) { + this.error = error; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + @Override + public String toString() { + return "TradeResponse [confirmationNumber=" + confirmationNumber + + ", error=" + error + ", errorMessage=" + errorMessage + + ", orderType=" + orderType + ", price=" + price + + ", quantity=" + quantity + ", ticker=" + ticker + "]"; + } + + + +} diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/gateway/MarketDataGateway.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/gateway/MarketDataGateway.java new file mode 100644 index 00000000..2ee93d4c --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/gateway/MarketDataGateway.java @@ -0,0 +1,26 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.gateway; + +/** + * Gateway interface for sending market data to clients + * @author Mark Pollack + * + */ +public interface MarketDataGateway { + + void sendMarketData(); +} diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/gateway/RabbitMarketDataGateway.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/gateway/RabbitMarketDataGateway.java new file mode 100644 index 00000000..32991054 --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/gateway/RabbitMarketDataGateway.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.gateway; + +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.amqp.rabbit.core.support.RabbitGatewaySupport; +import org.springframework.amqp.rabbit.stocks.domain.Quote; +import org.springframework.amqp.rabbit.stocks.domain.Stock; +import org.springframework.amqp.rabbit.stocks.domain.StockExchange; + +/** + * Rabbit implementation of the {@link MarketDataGateway} for sending Market data. + * + * @author Mark Pollack + * @author Mark Fisher + */ +public class RabbitMarketDataGateway extends RabbitGatewaySupport implements MarketDataGateway { + + private static Log logger = LogFactory.getLog(RabbitMarketDataGateway.class); + + private static final Random random = new Random(); + + private final List stocks = new ArrayList(); + + + public RabbitMarketDataGateway() { + this.stocks.add(new MockStock("AAPL", StockExchange.nasdaq, 255)); + this.stocks.add(new MockStock("CSCO", StockExchange.nasdaq, 22)); + this.stocks.add(new MockStock("DELL", StockExchange.nasdaq, 15)); + this.stocks.add(new MockStock("GOOG", StockExchange.nasdaq, 500)); + this.stocks.add(new MockStock("INTC", StockExchange.nasdaq, 22)); + this.stocks.add(new MockStock("MSFT", StockExchange.nasdaq, 29)); + this.stocks.add(new MockStock("ORCL", StockExchange.nasdaq, 24)); + this.stocks.add(new MockStock("CAJ", StockExchange.nyse, 43)); + this.stocks.add(new MockStock("F", StockExchange.nyse, 12)); + this.stocks.add(new MockStock("GE", StockExchange.nyse, 18)); + this.stocks.add(new MockStock("HMC", StockExchange.nyse, 32)); + this.stocks.add(new MockStock("HPQ", StockExchange.nyse, 48)); + this.stocks.add(new MockStock("IBM", StockExchange.nyse, 130)); + this.stocks.add(new MockStock("TM", StockExchange.nyse, 76)); + } + + + public void sendMarketData() { + Quote quote = generateFakeQuote(); + Stock stock = quote.getStock(); + logger.info("Sending Market Data for " + stock.getTicker()); + String routingKey = "app.stock.quotes."+ stock.getStockExchange() + "." + stock.getTicker(); + getRabbitTemplate().convertAndSend(routingKey, quote); + } + + private Quote generateFakeQuote() { + MockStock stock = this.stocks.get(random.nextInt(this.stocks.size())); + String price = stock.randomPrice(); + return new Quote(stock, price); + } + + + private static class MockStock extends Stock { + + private final int basePrice; + private final DecimalFormat twoPlacesFormat = new DecimalFormat("0.00"); + + private MockStock(String ticker, StockExchange stockExchange, int basePrice) { + this.setTicker(ticker); + this.setStockExchange(stockExchange); + this.basePrice = basePrice; + } + + private String randomPrice() { + return this.twoPlacesFormat.format(this.basePrice + Math.abs(random.nextGaussian())); + } + } + +} diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/gateway/RabbitStockServiceGateway.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/gateway/RabbitStockServiceGateway.java new file mode 100644 index 00000000..f7c2ecb2 --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/gateway/RabbitStockServiceGateway.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.gateway; + +import java.io.UnsupportedEncodingException; +import java.util.UUID; + +import org.springframework.amqp.AmqpException; +import org.springframework.amqp.core.Address; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessagePostProcessor; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.rabbit.core.support.RabbitGatewaySupport; +import org.springframework.amqp.rabbit.stocks.domain.TradeRequest; + +/** + * Rabbit implementation of {@link StockServiceGateway} to send trade requests to an external + * process. + * @author Mark Pollack + * + */ +public class RabbitStockServiceGateway extends RabbitGatewaySupport implements + StockServiceGateway { + + private String defaultReplyToQueue; + + public void setDefaultReplyToQueue(String defaultReplyToQueue) { + this.defaultReplyToQueue = defaultReplyToQueue; + } + + public void setDefaultReplyToQueue(Queue defaultReplyToQueue) { + this.defaultReplyToQueue = defaultReplyToQueue.getName(); + } + + + public void send(TradeRequest tradeRequest) { + + getRabbitTemplate().convertAndSend(tradeRequest, new MessagePostProcessor() { + + public Message postProcessMessage(Message message) throws AmqpException { + message.getMessageProperties().setReplyTo(new Address(defaultReplyToQueue)); + //bytes = ((String) object).getBytes(this.defaultCharset); + try { + message.getMessageProperties().setCorrelationId(UUID.randomUUID().toString().getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return message; + } + + }); + + } + +} diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/gateway/StockServiceGateway.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/gateway/StockServiceGateway.java new file mode 100644 index 00000000..e46610b8 --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/gateway/StockServiceGateway.java @@ -0,0 +1,29 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.gateway; + +import org.springframework.amqp.rabbit.stocks.domain.TradeRequest; + +/** + * Gateway interface that sends trades to an external process. + * @author Mark Pollack + * + */ +public interface StockServiceGateway { + + + void send(TradeRequest tradeRequest); +} diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/handler/ClientHandler.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/handler/ClientHandler.java new file mode 100644 index 00000000..e7138e70 --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/handler/ClientHandler.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.handler; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.amqp.rabbit.stocks.domain.Quote; +import org.springframework.amqp.rabbit.stocks.domain.Stock; +import org.springframework.amqp.rabbit.stocks.domain.TradeResponse; +import org.springframework.amqp.rabbit.stocks.ui.StockController; + +/** + * POJO handler that receives market data and trade responses. Calls are delegated to the UI controller. + * + * @author Mark Pollack + * @author Mark Fisher + */ +public class ClientHandler { + + private static Log log = LogFactory.getLog(ClientHandler.class); + + private StockController stockController; + + public StockController getStockController() { + return stockController; + } + + public void setStockController(StockController stockController) { + this.stockController = stockController; + } + + public void handleMessage(Quote quote) { + Stock stock = quote.getStock(); + log.info("Received market data. Ticker = " + stock.getTicker() + ", Price = " + quote.getPrice()); + stockController.displayQuote(quote); + } + + public void handleMessage(TradeResponse tradeResponse) { + log.info("Received trade repsonse. [" + tradeResponse + "]"); + stockController.UpdateTrade(tradeResponse); + } + +} diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/handler/ServerHandler.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/handler/ServerHandler.java new file mode 100644 index 00000000..9911b58b --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/handler/ServerHandler.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.handler; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.amqp.rabbit.stocks.domain.TradeRequest; +import org.springframework.amqp.rabbit.stocks.domain.TradeResponse; +import org.springframework.amqp.rabbit.stocks.service.CreditCheckService; +import org.springframework.amqp.rabbit.stocks.service.ExecutionVenueService; +import org.springframework.amqp.rabbit.stocks.service.TradingService; +import org.springframework.util.StringUtils; + + +/** + * POJO handler that receives trade requests and sends back a trade response. Main application + * logic sits here which coordinates between {@link ExecutionVenueService}, {@link CreditCheckService}, + * and {@link TradingService}. + * + * @author Mark Pollack + * + */ +public class ServerHandler { + + private ExecutionVenueService executionVenueService; + + private CreditCheckService creditCheckService; + + private TradingService tradingService; + + + + public ServerHandler(ExecutionVenueService executionVenueService, + CreditCheckService creditCheckService, + TradingService tradingService) { + this.executionVenueService = executionVenueService; + this.creditCheckService = creditCheckService; + this.tradingService = tradingService; + } + + public TradeResponse handleMessage(TradeRequest tradeRequest) + { + TradeResponse tradeResponse; + List errors = new ArrayList(); + if (creditCheckService.canExecute(tradeRequest, errors)) + { + tradeResponse = executionVenueService.executeTradeRequest(tradeRequest); + } + else + { + tradeResponse = new TradeResponse(); + tradeResponse.setError(true); + tradeResponse.setErrorMessage(StringUtils.arrayToCommaDelimitedString(errors.toArray())); + + } + tradingService.processTrade(tradeRequest, tradeResponse); + return tradeResponse; + } + + + +} diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/service/CreditCheckService.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/service/CreditCheckService.java new file mode 100644 index 00000000..9bbaed1e --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/service/CreditCheckService.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.service; + +import java.util.List; + +import org.springframework.amqp.rabbit.stocks.domain.TradeRequest; + +/** + * Credit service to see if the incoming trade can be processed. If it can not be processed + * a false value is returned and the error list contains information as to what went wrong. + * + * @author Mark Pollack + * + */ +public interface CreditCheckService { + + boolean canExecute(TradeRequest tradeRequest, List errors); +} diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/service/ExecutionVenueService.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/service/ExecutionVenueService.java new file mode 100644 index 00000000..7896ca9d --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/service/ExecutionVenueService.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.service; + +import org.springframework.amqp.rabbit.stocks.domain.TradeRequest; +import org.springframework.amqp.rabbit.stocks.domain.TradeResponse; + +/** + * Executes the trade request, creating a Trade response. See the code flow in {@link ServerHandler} for + * its usage. + * @author Mark Pollack + * + */ +public interface ExecutionVenueService { + + TradeResponse executeTradeRequest(TradeRequest request); +} diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/service/TradingService.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/service/TradingService.java new file mode 100644 index 00000000..e3889ad4 --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/service/TradingService.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.service; + +import org.springframework.amqp.rabbit.stocks.domain.TradeRequest; +import org.springframework.amqp.rabbit.stocks.domain.TradeResponse; + +/** + * Trading Service to process trade requests and response. This is the place to perform + * any trade processing after executions. See code flow in {@link ServerHandler}. + * + * @author Mark Pollack + * + */ +public interface TradingService { + + void processTrade(TradeRequest request, TradeResponse response); +} diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/service/stubs/CreditCheckServiceStub.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/service/stubs/CreditCheckServiceStub.java new file mode 100644 index 00000000..4507fe11 --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/service/stubs/CreditCheckServiceStub.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.service.stubs; + +import java.util.List; + +import org.springframework.amqp.rabbit.stocks.domain.TradeRequest; +import org.springframework.amqp.rabbit.stocks.service.CreditCheckService; + +/*** + * An implementation that always returns true (just like real-life for a mortgage check :) + * + * @author Mark Pollack + * + */ +public class CreditCheckServiceStub implements CreditCheckService { + + public boolean canExecute(TradeRequest tradeRequest, List errors) { + return true; + } + +} diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/service/stubs/ExecutionVenueServiceStub.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/service/stubs/ExecutionVenueServiceStub.java new file mode 100644 index 00000000..e7d6b06c --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/service/stubs/ExecutionVenueServiceStub.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.service.stubs; + +import java.math.BigDecimal; +import java.util.Random; +import java.util.UUID; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.amqp.rabbit.stocks.domain.TradeRequest; +import org.springframework.amqp.rabbit.stocks.domain.TradeResponse; +import org.springframework.amqp.rabbit.stocks.service.ExecutionVenueService; + +/** + * Execute the trade, setting the execution price to changing value in line with what the market data feed is producing. + * + * @author Mark Pollack + * + */ +public class ExecutionVenueServiceStub implements ExecutionVenueService { + + private static Log log = LogFactory.getLog(ExecutionVenueServiceStub.class); + + private Random random = new Random(); + + public TradeResponse executeTradeRequest(TradeRequest request) { + TradeResponse response = new TradeResponse(); + response.setOrderType(request.getOrderType()); + response.setPrice(calculatePrice(request.getTicker(), request.getQuantity(), request.getOrderType(), request.getPrice(), request.getUserName())); + response.setQuantity(request.getQuantity()); + response.setTicker(request.getTicker()); + response.setConfirmationNumber(UUID.randomUUID().toString()); + + + try { + log.info("Sleeping 2 seconds to simulate processing.."); + Thread.sleep(2000); + } catch (InterruptedException e) { + log.error("Didn't finish sleeping", e); + } + return response; + } + + private BigDecimal calculatePrice(String ticker, long quantity, + String orderType, BigDecimal price, String userName) { + // provide as sophisticated implementation...for now all the same price. + if (orderType.compareTo("LIMIT") == 0) + { + return price; + } + else + { + //in line with market data implementation + return new BigDecimal(22 + Math.abs(gaussian())); + } + } + + private double gaussian() { + return random.nextGaussian(); + } + +} diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/service/stubs/TradingServiceStub.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/service/stubs/TradingServiceStub.java new file mode 100644 index 00000000..1cbe3bbe --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/service/stubs/TradingServiceStub.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.service.stubs; + +import org.springframework.amqp.rabbit.stocks.domain.TradeRequest; +import org.springframework.amqp.rabbit.stocks.domain.TradeResponse; +import org.springframework.amqp.rabbit.stocks.service.TradingService; + +/** + * No-op implementation + * @author Mark Pollack + * + */ +public class TradingServiceStub implements TradingService { + + public void processTrade(TradeRequest request, TradeResponse response) { + + } + +} diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/ui/StockController.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/ui/StockController.java new file mode 100644 index 00000000..4af67198 --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/ui/StockController.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.ui; + +import org.springframework.amqp.rabbit.stocks.domain.Quote; +import org.springframework.amqp.rabbit.stocks.domain.TradeRequest; +import org.springframework.amqp.rabbit.stocks.domain.TradeResponse; +import org.springframework.amqp.rabbit.stocks.gateway.StockServiceGateway; + +/** + * Basic controller for the UI. + * TODO: Fix that the UI can receive events before it's panel has been initialized. + * + * @author Mark Pollack + * @author Mark Fisher + */ +public class StockController { + + private StockPanel stockPanel; + + private StockServiceGateway stockServiceGateway; + + + public StockPanel getStockPanel() { + return stockPanel; + } + + public void setStockPanel(StockPanel stockPanel) { + this.stockPanel = stockPanel; + } + + public StockServiceGateway getStockServiceGateway() { + return stockServiceGateway; + } + + public void setStockServiceGateway(StockServiceGateway stockServiceGateway) { + this.stockServiceGateway = stockServiceGateway; + } + + // "Actions" + + public void sendTradeRequest(String text) { + String[] tokens = text.split("\\s"); + String quantityString = tokens[0]; + String ticker = tokens[1]; + int quantity = Integer.parseInt(quantityString); + TradeRequest tr = new TradeRequest(); + tr.setAccountName("ACCT-123"); + tr.setBuyRequest(true); + tr.setOrderType("MARKET"); + tr.setTicker(ticker); + tr.setQuantity(quantity); + tr.setRequestId("REQ-1"); + tr.setUserName("Joe Trader"); + tr.setUserName("Joe"); + stockServiceGateway.send(tr); + } + + public void displayQuote(Quote quote) { + //TODO race condition with message delivery and initalization... use @Configurable? + if (stockPanel != null) { + stockPanel.displayQuote(quote); + } + } + + public void UpdateTrade(TradeResponse tradeResponse) { + stockPanel.update(tradeResponse); + } + +} diff --git a/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/ui/StockPanel.java b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/ui/StockPanel.java new file mode 100644 index 00000000..66626fa1 --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/java/org/springframework/amqp/rabbit/stocks/ui/StockPanel.java @@ -0,0 +1,144 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks.ui; + +import java.awt.Color; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.text.DecimalFormat; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.SwingUtilities; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.amqp.rabbit.stocks.domain.Quote; +import org.springframework.amqp.rabbit.stocks.domain.Stock; +import org.springframework.amqp.rabbit.stocks.domain.TradeResponse; + +import com.jgoodies.forms.layout.CellConstraints; +import com.jgoodies.forms.layout.FormLayout; + +/** + * A typical poor mans UI to drive the application. + * + * @author Mark Pollack + * @author Mark Fisher + */ +public class StockPanel extends JPanel { + + private static Log log = LogFactory.getLog(StockPanel.class); + + private JTextField tradeRequestTextField; + private JButton tradeRequestButton; + private JTextArea marketDataTextArea; + private StockController stockController; + + private DecimalFormat frmt = new DecimalFormat("$0.00"); + + public StockPanel(StockController controller) { + this.stockController = controller; + controller.setStockPanel(this); + this.setBorder(BorderFactory.createTitledBorder("Stock Form")); + + FormLayout formLayout = new FormLayout("pref, 150dlu", // columns + "pref, fill:100dlu:grow"); // rows + setLayout(formLayout); + CellConstraints c = new CellConstraints(); + + tradeRequestButton = new JButton("Send Trade Request"); + add(tradeRequestButton, c.xy(1, 1)); + + tradeRequestTextField = new JTextField(""); + add(tradeRequestTextField, c.xy(2, 1)); + + add(new JLabel("Market Data"), c.xy(1, 2)); + + marketDataTextArea = new JTextArea(); + JScrollPane sp = new JScrollPane(marketDataTextArea); + sp.setSize(200, 300); + + add(sp, c.xy(2, 2)); + + tradeRequestTextField.addFocusListener(new FocusListener() { + public void focusLost(FocusEvent e) { + } + public void focusGained(FocusEvent e) { + tradeRequestTextField.setText(""); + tradeRequestTextField.setForeground(Color.BLACK); + } + }); + + tradeRequestButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + sendTradeRequest(); + } + }); + } + + private void sendTradeRequest() { + try { + stockController.sendTradeRequest(tradeRequestTextField.getText()); + tradeRequestTextField.setForeground(Color.GRAY); + tradeRequestTextField.setText("Request Pending..."); + log.info("Sent trade request."); + } + catch (Exception ex) { + tradeRequestTextField.setForeground(Color.RED); + tradeRequestTextField.setText("Required Format: 100 TCKR"); + } + } + + public static void main(String[] a) { + JFrame f = new JFrame("Rabbit Stock Demo"); + f.setDefaultCloseOperation(2); + f.add(new StockPanel(new StockController())); + f.pack(); + f.setVisible(true); + } + + public void displayQuote(final Quote quote) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + Stock stock = quote.getStock(); + marketDataTextArea.append(stock.getTicker() + " " + quote.getPrice() + "\n"); + } + }); + } + + public void update(final TradeResponse tradeResponse) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + tradeRequestTextField.setForeground(Color.GREEN); + tradeRequestTextField.setText("Confirmed. " + + tradeResponse.getTicker() + " " + + frmt.format(tradeResponse.getPrice().doubleValue())); + } + }); + } + +} diff --git a/spring-amqp-samples/stocks/src/main/resources/client-bootstrap-config.xml b/spring-amqp-samples/stocks/src/main/resources/client-bootstrap-config.xml new file mode 100644 index 00000000..3a2f9cd7 --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/resources/client-bootstrap-config.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/spring-amqp-samples/stocks/src/main/resources/client-handlers.xml b/spring-amqp-samples/stocks/src/main/resources/client-handlers.xml new file mode 100644 index 00000000..94a5d616 --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/resources/client-handlers.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/spring-amqp-samples/stocks/src/main/resources/client-messaging.xml b/spring-amqp-samples/stocks/src/main/resources/client-messaging.xml new file mode 100644 index 00000000..6c942a69 --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/resources/client-messaging.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-amqp-samples/stocks/src/main/resources/client.properties b/spring-amqp-samples/stocks/src/main/resources/client.properties new file mode 100644 index 00000000..077f318a --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/resources/client.properties @@ -0,0 +1 @@ +stocks.quote.pattern=app.stock.quotes.nasdaq.* \ No newline at end of file diff --git a/spring-amqp-samples/stocks/src/main/resources/server-bootstrap-config.xml b/spring-amqp-samples/stocks/src/main/resources/server-bootstrap-config.xml new file mode 100644 index 00000000..129730d0 --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/resources/server-bootstrap-config.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + diff --git a/spring-amqp-samples/stocks/src/main/resources/server-handlers.xml b/spring-amqp-samples/stocks/src/main/resources/server-handlers.xml new file mode 100644 index 00000000..3f97d4df --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/resources/server-handlers.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/spring-amqp-samples/stocks/src/main/resources/server-jmx.xml b/spring-amqp-samples/stocks/src/main/resources/server-jmx.xml new file mode 100644 index 00000000..c61a8b8e --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/resources/server-jmx.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/spring-amqp-samples/stocks/src/main/resources/server-messaging.xml b/spring-amqp-samples/stocks/src/main/resources/server-messaging.xml new file mode 100644 index 00000000..ec761908 --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/resources/server-messaging.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/spring-amqp-samples/stocks/src/main/resources/server-services.xml b/spring-amqp-samples/stocks/src/main/resources/server-services.xml new file mode 100644 index 00000000..dea70279 --- /dev/null +++ b/spring-amqp-samples/stocks/src/main/resources/server-services.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-amqp-samples/stocks/src/site/site.xml b/spring-amqp-samples/stocks/src/site/site.xml new file mode 100644 index 00000000..4bba09c7 --- /dev/null +++ b/spring-amqp-samples/stocks/src/site/site.xml @@ -0,0 +1,25 @@ + + + + + Spring Batch: ${project.name} + index.html + + + + org.springframework.maven.skins + maven-spring-skin + 1.0.5 + + + + + + + + +

+ + + + diff --git a/spring-amqp-samples/stocks/src/test/java/org/springframework/amqp/rabbit/stocks/Client.java b/spring-amqp-samples/stocks/src/test/java/org/springframework/amqp/rabbit/stocks/Client.java new file mode 100644 index 00000000..94131f72 --- /dev/null +++ b/spring-amqp-samples/stocks/src/test/java/org/springframework/amqp/rabbit/stocks/Client.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks; + + +import javax.swing.JFrame; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.junit.Test; +import org.springframework.amqp.rabbit.stocks.ui.StockController; +import org.springframework.amqp.rabbit.stocks.ui.StockPanel; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * Main client application, can run as app or unit test. + * @author Mark Pollack + * + */ +public class Client { + + private static Log log = LogFactory.getLog(Client.class); + + /** + * @param args + */ + public static void main(String[] args) { + new Client().run(); + } + + @Test + public void run() { + + ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("client-bootstrap-config.xml"); + + StockController controller = ac.getBean(StockController.class); + + JFrame f = new JFrame("Rabbit Stock Demo"); + f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + //TODO consider @Configurable + f.add(new StockPanel(controller)); + f.pack(); + f.setVisible(true); + } + + + + +} diff --git a/spring-amqp-samples/stocks/src/test/java/org/springframework/amqp/rabbit/stocks/Server.java b/spring-amqp-samples/stocks/src/test/java/org/springframework/amqp/rabbit/stocks/Server.java new file mode 100644 index 00000000..3c55c9c5 --- /dev/null +++ b/spring-amqp-samples/stocks/src/test/java/org/springframework/amqp/rabbit/stocks/Server.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.stocks; + +import org.junit.Test; + +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * Server application than can be run as an app or unit test. + * + * @author Mark Pollack + */ +public class Server { + + public static void main(String[] args) { + new Server().run(); + } + + @Test + public void run() { + new ClassPathXmlApplicationContext("server-bootstrap-config.xml"); + } + +} diff --git a/spring-amqp-samples/stocks/src/test/resources/commons-logging.properties b/spring-amqp-samples/stocks/src/test/resources/commons-logging.properties new file mode 100644 index 00000000..51cd942d --- /dev/null +++ b/spring-amqp-samples/stocks/src/test/resources/commons-logging.properties @@ -0,0 +1,7 @@ +# Use Log4j +priority=1 +org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.LogFactoryImpl +org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger + +# Configuration file of the log +#log4j.configuration=file:log4j-rabbit-stocks.properties diff --git a/spring-amqp-samples/stocks/src/test/resources/log4j.properties b/spring-amqp-samples/stocks/src/test/resources/log4j.properties new file mode 100644 index 00000000..9687c70d --- /dev/null +++ b/spring-amqp-samples/stocks/src/test/resources/log4j.properties @@ -0,0 +1,11 @@ +log4j.rootCategory=INFO, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + +#log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n +log4j.appender.stdout.layout.ConversionPattern=%-5p [%40.40c{4}]: %m%n + +log4j.category.org.springframework.amqp.rabbit=DEBUG +log4j.category.org.springframework.beans.factory=INFO + diff --git a/spring-otp/.classpath b/spring-otp/.classpath new file mode 100644 index 00000000..7a0eedb4 --- /dev/null +++ b/spring-otp/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/spring-otp/.project b/spring-otp/.project new file mode 100644 index 00000000..01f3e555 --- /dev/null +++ b/spring-otp/.project @@ -0,0 +1,35 @@ + + + spring-otp + + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.maven.ide.eclipse.maven2Builder + + + + + org.springframework.ide.eclipse.core.springbuilder + + + + + + org.springframework.ide.eclipse.core.springnature + org.maven.ide.eclipse.maven2Nature + org.eclipse.jdt.core.javanature + org.eclipse.wst.common.project.facet.core.nature + + diff --git a/spring-otp/.settings/org.eclipse.jdt.core.prefs b/spring-otp/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..a8f84131 --- /dev/null +++ b/spring-otp/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,6 @@ +#Tue Jun 29 21:59:29 EDT 2010 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.5 diff --git a/spring-otp/.settings/org.maven.ide.eclipse.prefs b/spring-otp/.settings/org.maven.ide.eclipse.prefs new file mode 100644 index 00000000..7ccc9920 --- /dev/null +++ b/spring-otp/.settings/org.maven.ide.eclipse.prefs @@ -0,0 +1,9 @@ +#Tue Jun 29 21:59:00 EDT 2010 +activeProfiles= +eclipse.preferences.version=1 +fullBuildGoals=process-test-resources +includeModules=false +resolveWorkspaceProjects=true +resourceFilterGoals=process-resources resources\:testResources +skipCompilerPlugin=true +version=1 diff --git a/spring-otp/.springBeans b/spring-otp/.springBeans new file mode 100644 index 00000000..dbe284d5 --- /dev/null +++ b/spring-otp/.springBeans @@ -0,0 +1,13 @@ + + + 1 + + + + + + + + + + diff --git a/spring-otp/pom.xml b/spring-otp/pom.xml new file mode 100644 index 00000000..274fc378 --- /dev/null +++ b/spring-otp/pom.xml @@ -0,0 +1,103 @@ + + 4.0.0 + + org.springframework.amqp + spring-amqp-parent + 1.0.0.BUILD-SNAPSHOT + ../spring-amqp-parent/pom.xml + + spring-otp + jar + Spring OTP Support + + + + + org.springframework + spring-beans + + + + + commons-io + commons-io + + + + + org.slf4j + slf4j-api + + + org.slf4j + jcl-over-slf4j + runtime + + + org.slf4j + slf4j-log4j12 + runtime + + + log4j + log4j + + + javax.mail + mail + + + javax.jms + jms + + + com.sun.jdmk + jmxtools + + + com.sun.jmx + jmxri + + + runtime + + + + javax.annotation + jsr250-api + true + + + + cglib + cglib-nodep + true + + + + org.mockito + mockito-all + test + + + + junit + junit + + + + org.erlang.otp + jinterface + + + + + + + com.springsource.bundlor + com.springsource.bundlor.maven + + + + diff --git a/spring-otp/src/main/java/org/springframework/otp/erlang/ErlangBadRpcException.java b/spring-otp/src/main/java/org/springframework/otp/erlang/ErlangBadRpcException.java new file mode 100644 index 00000000..1d084348 --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/otp/erlang/ErlangBadRpcException.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2010 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.otp.erlang; + +import com.ericsson.otp.erlang.OtpErlangTuple; + +/** + * Exception thrown when an 'badrpc' is received from an Erlang RPC call. + * @author Mark Pollack + * + */ +public class ErlangBadRpcException extends OtpException { + + private OtpErlangTuple reasonTuple; + + public ErlangBadRpcException(String reason) { + super(reason); + } + + public ErlangBadRpcException(OtpErlangTuple tuple) { + super(tuple.toString()); + this.reasonTuple = tuple; + } + + public OtpErlangTuple getReasonTuple() { + return reasonTuple; + } + + + + + + + +} diff --git a/spring-otp/src/main/java/org/springframework/otp/erlang/ErlangErrorRpcException.java b/spring-otp/src/main/java/org/springframework/otp/erlang/ErlangErrorRpcException.java new file mode 100644 index 00000000..cf28835a --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/otp/erlang/ErlangErrorRpcException.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2010 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.otp.erlang; + +import com.ericsson.otp.erlang.OtpErlangTuple; + +/** + * Exception thrown when an 'error' is received from an Erlang RPC call + * @author Mark Pollack + * + */ +public class ErlangErrorRpcException extends OtpException { + + private OtpErlangTuple reasonTuple; + + public ErlangErrorRpcException(String message) { + super(message); + } + + public ErlangErrorRpcException(OtpErlangTuple tuple) { + super(tuple.toString()); + this.reasonTuple = tuple; + } + + public OtpErlangTuple getReasonTuple() { + return reasonTuple; + } + + +} diff --git a/spring-otp/src/main/java/org/springframework/otp/erlang/OtpAuthException.java b/spring-otp/src/main/java/org/springframework/otp/erlang/OtpAuthException.java new file mode 100644 index 00000000..765f6ab2 --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/otp/erlang/OtpAuthException.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2010 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.otp.erlang; + +/** + * Runtime exception mirroring the OTP OtpAuthException. + * + * @author Mark Pollack + */ +public class OtpAuthException extends OtpException { + + public OtpAuthException(com.ericsson.otp.erlang.OtpAuthException cause) { + super(cause); + } + +} diff --git a/spring-otp/src/main/java/org/springframework/otp/erlang/OtpException.java b/spring-otp/src/main/java/org/springframework/otp/erlang/OtpException.java new file mode 100644 index 00000000..6646632e --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/otp/erlang/OtpException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2010 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.otp.erlang; + +/** + * Base RuntimeException for errors that occur when executing OTP operations. + * + * @author Mark Pollack + */ +public class OtpException extends RuntimeException { + + public OtpException(String message) { + super(message); + } + + public OtpException(Throwable cause) { + super(cause); + } + + public OtpException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/spring-otp/src/main/java/org/springframework/otp/erlang/OtpIOException.java b/spring-otp/src/main/java/org/springframework/otp/erlang/OtpIOException.java new file mode 100644 index 00000000..56ecdeff --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/otp/erlang/OtpIOException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2010 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.otp.erlang; + +import java.io.IOException; + +/** + * RuntimeException wrapper for an {@link IOException} which + * can be commonly thrown from OTP operations. + * + * @author Mark Pollack + * @author Mark Fisher + */ +public class OtpIOException extends OtpException { + + public OtpIOException(IOException cause) { + super(cause); + } + + public OtpIOException(String message, IOException cause) { + super(message, cause); + } + +} diff --git a/spring-otp/src/main/java/org/springframework/otp/erlang/UncategorizedOtpException.java b/spring-otp/src/main/java/org/springframework/otp/erlang/UncategorizedOtpException.java new file mode 100644 index 00000000..c6292027 --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/otp/erlang/UncategorizedOtpException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2010 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.otp.erlang; + +/** + * A "catch-all" exception type within the OtpException hierarchy + * when no more specific cause is known. + * + * @author Mark Pollack + */ +public class UncategorizedOtpException extends OtpException { + + public UncategorizedOtpException(Throwable cause) { + super(cause); + } + + public UncategorizedOtpException(String message, Throwable cause) { + super(message, cause); + } + +} \ No newline at end of file diff --git a/spring-otp/src/main/java/org/springframework/otp/erlang/connection/ConnectionFactory.java b/spring-otp/src/main/java/org/springframework/otp/erlang/connection/ConnectionFactory.java new file mode 100644 index 00000000..41db9a92 --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/otp/erlang/connection/ConnectionFactory.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2010 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.otp.erlang.connection; + +import java.io.IOException; +import java.net.UnknownHostException; + +import com.ericsson.otp.erlang.OtpAuthException; +import com.ericsson.otp.erlang.OtpConnection; + +/** + * An interface based ConnectionFactory for creating {@link OtpConnection}s. + * + *

NOTE: The Rabbit API contains a ConnectionFactory class (same name). + * + * @author Mark Pollack + */ +public interface ConnectionFactory { + + OtpConnection createConnection() throws UnknownHostException, OtpAuthException, IOException; + +} diff --git a/spring-otp/src/main/java/org/springframework/otp/erlang/connection/ConnectionFactoryUtils.java b/spring-otp/src/main/java/org/springframework/otp/erlang/connection/ConnectionFactoryUtils.java new file mode 100644 index 00000000..57ea26bf --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/otp/erlang/connection/ConnectionFactoryUtils.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2010 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.otp.erlang.connection; + + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.ericsson.otp.erlang.OtpConnection; + +/** + * @author Mark Pollack + */ +public class ConnectionFactoryUtils { + + private static final Log logger = LogFactory.getLog(ConnectionFactoryUtils.class); + + + /** + * Release the given Connection by closing it. + */ + public static void releaseConnection(OtpConnection con, ConnectionFactory cf) { + if (con == null) { + return; + } + try { + con.close(); + } + catch (Throwable ex) { + logger.debug("Could not close Otp Connection", ex); + } + } + +} diff --git a/spring-otp/src/main/java/org/springframework/otp/erlang/connection/ConnectionParameters.java b/spring-otp/src/main/java/org/springframework/otp/erlang/connection/ConnectionParameters.java new file mode 100644 index 00000000..07d9b24e --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/otp/erlang/connection/ConnectionParameters.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2010 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.otp.erlang.connection; + +import com.ericsson.otp.erlang.OtpPeer; +import com.ericsson.otp.erlang.OtpSelf; + +/** + * Encapsulate properties to create a OtpConnection + * @author Mark Pollack + * + */ +public class ConnectionParameters { + + private OtpSelf otpSelf; + + private OtpPeer otpPeer; + + public ConnectionParameters(OtpSelf otpSelf, OtpPeer otpPeer) { + //TODO assert not null... + this.otpSelf = otpSelf; + this.otpPeer = otpPeer; + } + + public OtpSelf getOtpSelf() { + return otpSelf; + } + + public OtpPeer getOtpPeer() { + return otpPeer; + } + +} diff --git a/spring-otp/src/main/java/org/springframework/otp/erlang/connection/SimpleConnectionFactory.java b/spring-otp/src/main/java/org/springframework/otp/erlang/connection/SimpleConnectionFactory.java new file mode 100644 index 00000000..ae6c15dc --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/otp/erlang/connection/SimpleConnectionFactory.java @@ -0,0 +1,87 @@ +/* + * Copyright 2002-2010 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.otp.erlang.connection; + +import java.io.IOException; +import java.net.UnknownHostException; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.otp.erlang.OtpIOException; +import org.springframework.util.Assert; + +import com.ericsson.otp.erlang.OtpAuthException; +import com.ericsson.otp.erlang.OtpConnection; +import com.ericsson.otp.erlang.OtpPeer; +import com.ericsson.otp.erlang.OtpSelf; + +/** + * @author Mark Pollack + * @author Mark Fisher + */ +public class SimpleConnectionFactory implements ConnectionFactory, InitializingBean { + + private String selfNodeName; + + private String cookie; + + private String peerNodeName; + + private OtpSelf otpSelf; + + private OtpPeer otpPeer; + + + public SimpleConnectionFactory(String selfNodeName, String cookie, String peerNodeName) { + this.selfNodeName = selfNodeName; + this.cookie = cookie; + this.peerNodeName = peerNodeName; + } + + public SimpleConnectionFactory(String selfNodeName, String peerNodeName) { + this.selfNodeName = selfNodeName; + this.peerNodeName = peerNodeName; + } + + + public OtpConnection createConnection() throws UnknownHostException, OtpAuthException, IOException { + try { + return otpSelf.connect(otpPeer); + } + catch (IOException ex) { + throw new OtpIOException("failed to connect from '" + this.selfNodeName + + "' to peer node '" + this.peerNodeName + "'", ex); + } + } + + public void afterPropertiesSet() { + Assert.isTrue(this.selfNodeName != null || this.peerNodeName != null, + "'selfNodeName' or 'peerNodeName' is required"); + try { + if (this.cookie == null) { + this.otpSelf = new OtpSelf(this.selfNodeName); + } + else { + this.otpSelf = new OtpSelf(this.selfNodeName, this.cookie); + } + } + catch (IOException e) { + throw new OtpIOException(e); + } + this.otpPeer = new OtpPeer(this.peerNodeName); + } + +} diff --git a/spring-otp/src/main/java/org/springframework/otp/erlang/core/Application.java b/spring-otp/src/main/java/org/springframework/otp/erlang/core/Application.java new file mode 100644 index 00000000..550c6343 --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/otp/erlang/core/Application.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2010 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.otp.erlang.core; + +/** + * Describes an Erlang application. Only three fields are supported as that is the level + * of information that rabbitmq returns when performing a status request. + * + * See http://www.erlang.org/doc/man/app.html for full details + * + * @author Mark Pollack + * + */ +public class Application { + + private String description; + + private String id; + + private String version; + + public Application(String description, String id, String version) { + super(); + this.description = description; + this.id = id; + this.version = version; + } + + public String getDescription() { + return description; + } + + public String getId() { + return id; + } + + + public String getVersion() { + return version; + } + + @Override + public String toString() { + return "Application [description=" + description + ", id=" + id + + ", version=" + version + "]"; + } + + + +} diff --git a/spring-otp/src/main/java/org/springframework/otp/erlang/core/ConnectionCallback.java b/spring-otp/src/main/java/org/springframework/otp/erlang/core/ConnectionCallback.java new file mode 100644 index 00000000..6d94521d --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/otp/erlang/core/ConnectionCallback.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2010 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.otp.erlang.core; + +import com.ericsson.otp.erlang.OtpConnection; + +/** + * Basic callback for use in ErlangTemplate + * @author Mark Pollack + */ +public interface ConnectionCallback { + + /** + * Execute any number of operations against the supplied OTP connection, + * possibly returning a result. + */ + T doInConnection(OtpConnection connection) throws Exception; //Not sure everything it throws + +} diff --git a/spring-otp/src/main/java/org/springframework/otp/erlang/core/ErlangOperations.java b/spring-otp/src/main/java/org/springframework/otp/erlang/core/ErlangOperations.java new file mode 100644 index 00000000..1d62de6e --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/otp/erlang/core/ErlangOperations.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2010 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.otp.erlang.core; + +import org.springframework.otp.erlang.OtpException; +import org.springframework.otp.erlang.support.converter.ErlangConverter; + +import com.ericsson.otp.erlang.OtpErlangList; +import com.ericsson.otp.erlang.OtpErlangObject; + +/** + * Operations to perform against OTP/Erlang + * @author Mark Pollack + * + */ +public interface ErlangOperations { + + T execute(ConnectionCallback action) throws OtpException; + + OtpErlangObject executeErlangRpc(String module, String function, OtpErlangList args) throws OtpException; + + + OtpErlangObject executeErlangRpc(String module, String function, OtpErlangObject... args) throws OtpException; + + + OtpErlangObject executeRpc(String module, String function, Object... args) throws OtpException; + + Object executeAndConvertRpc(String module, String function, ErlangConverter converterToUse, Object... args) throws OtpException; + + Object executeAndConvertRpc(String module, String function, Object... args) throws OtpException; +} diff --git a/spring-otp/src/main/java/org/springframework/otp/erlang/core/ErlangTemplate.java b/spring-otp/src/main/java/org/springframework/otp/erlang/core/ErlangTemplate.java new file mode 100644 index 00000000..100c013a --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/otp/erlang/core/ErlangTemplate.java @@ -0,0 +1,156 @@ +/* + * Copyright 2002-2010 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.otp.erlang.core; + +import org.springframework.otp.erlang.ErlangErrorRpcException; +import org.springframework.otp.erlang.ErlangBadRpcException; +import org.springframework.otp.erlang.OtpException; +import org.springframework.otp.erlang.connection.ConnectionFactory; +import org.springframework.otp.erlang.support.ErlangAccessor; +import org.springframework.otp.erlang.support.ErlangUtils; +import org.springframework.otp.erlang.support.converter.ErlangConverter; +import org.springframework.otp.erlang.support.converter.SimpleErlangConverter; +import org.springframework.util.Assert; + +import com.ericsson.otp.erlang.*; + + +/** + * @author Mark Pollack + */ +public class ErlangTemplate extends ErlangAccessor implements ErlangOperations { + + + private volatile ErlangConverter erlangConverter = new SimpleErlangConverter(); + + public ErlangTemplate(ConnectionFactory connectionFactory) { + setConnectionFactory(connectionFactory); + afterPropertiesSet(); + } + + public OtpErlangObject executeErlangRpc(final String module, final String function, final OtpErlangList args) { + return execute(new ConnectionCallback() { + public OtpErlangObject doInConnection(OtpConnection connection) throws Exception { + logger.debug("Sending RPC for module [" + module + "] function [" + function + "] args [" + args); + connection.sendRPC(module, function, args); + //TODO consider dedicated response object. + OtpErlangObject response = connection.receiveRPC(); + logger.debug("Response received = " + response.toString()); + handleResponseError(module, function, response); + return response; + } + }); + } + + public void handleResponseError(String module, String function, OtpErlangObject result) { + //{badrpc,{'EXIT',{undef,[{rabbit_access_control,list_users,[[]]},{rpc,'-handle_call/3-fun-0-',5}]}}} + + if (result instanceof OtpErlangTuple) { + OtpErlangTuple msg = (OtpErlangTuple)result; + OtpErlangObject[] elements = msg.elements(); + if (msg.elementAt(0) instanceof OtpErlangAtom) + { + OtpErlangAtom responseAtom = (OtpErlangAtom)msg.elementAt(0); + //TODO consider error handler strategy. + if (responseAtom.atomValue().equals("badrpc")) { + if (msg.elementAt(1) instanceof OtpErlangTuple) { + throw new ErlangBadRpcException( (OtpErlangTuple)msg.elementAt(1)); + } else { + throw new ErlangBadRpcException( msg.elementAt(1).toString()); + } + } else if (responseAtom.atomValue().equals("error")) { + if (msg.elementAt(1) instanceof OtpErlangTuple) { + throw new ErlangErrorRpcException( (OtpErlangTuple)msg.elementAt(1)); + } else { + throw new ErlangErrorRpcException( msg.elementAt(1).toString()); + } + } + } + } + } + + public OtpErlangObject executeErlangRpc(String module, String function, OtpErlangObject... args) { + return executeRpc(module, function, new OtpErlangList(args)); + } + + public OtpErlangObject executeRpc(String module, String function, Object... args) { + return executeErlangRpc(module, function, (OtpErlangList) erlangConverter.toErlang(args)); + } + + public Object executeAndConvertRpc(String module, String function, ErlangConverter converterToUse, Object... args) { + return converterToUse.fromErlang(executeRpc(module, function, converterToUse.toErlang(args))); + } + + public Object executeAndConvertRpc(String module, String function, Object... args) { + return erlangConverter.fromErlangRpc(module, function, executeErlangRpc(module, function, (OtpErlangList)erlangConverter.toErlang(args))); + } + + public ErlangConverter getErlangConverter() { + return erlangConverter; + } + + public void setErlangConverter(ErlangConverter erlangConverter) { + this.erlangConverter = erlangConverter; + } + + public T execute(ConnectionCallback action) throws OtpException { + + Assert.notNull(action, "Callback object must not be null"); + OtpConnection con = null; + try { + con = createConnection(); + return action.doInConnection(con); + } + catch (OtpException ex) { + throw ex; + } + catch (Exception ex) { + throw convertOtpAccessException(ex); + } + finally { + org.springframework.otp.erlang.connection.ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory()); + } + + + + + } + + /** + * Convert the specified checked {@link javax.jms.JMSException JMSException} to + * a Spring runtime {@link org.springframework.jms.JmsException JmsException} + * equivalent. + *

The default implementation delegates to the + * {@link org.springframework.jms.support.JmsUtils#convertJmsAccessException} method. + * @param ex the original checked {@link JMSException} to convert + * @return the Spring runtime {@link JmsException} wrapping ex + * @see org.springframework.jms.support.JmsUtils#convertJmsAccessException + */ + protected OtpException convertOtpAccessException(Exception ex) { + return ErlangUtils.convertOtpAccessException(ex); + } + + + + + + + + + + +} diff --git a/spring-otp/src/main/java/org/springframework/otp/erlang/core/Node.java b/spring-otp/src/main/java/org/springframework/otp/erlang/core/Node.java new file mode 100644 index 00000000..e4c5ea17 --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/otp/erlang/core/Node.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2010 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. + */ + +/** + * Describes an Erlang node. + */ +package org.springframework.otp.erlang.core; + +/** + * Simple description class for an Erlang node. + * + * @author Mark Pollack + * + */ +public class Node { + + private String name; + + public Node(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return "Node [name=" + name + "]"; + } + +} diff --git a/spring-otp/src/main/java/org/springframework/otp/erlang/support/ErlangAccessor.java b/spring-otp/src/main/java/org/springframework/otp/erlang/support/ErlangAccessor.java new file mode 100644 index 00000000..9a7acd4b --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/otp/erlang/support/ErlangAccessor.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2010 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.otp.erlang.support; + +import java.io.IOException; +import java.net.UnknownHostException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.otp.erlang.OtpException; +import org.springframework.otp.erlang.connection.ConnectionFactory; + +import com.ericsson.otp.erlang.OtpAuthException; +import com.ericsson.otp.erlang.OtpConnection; + +/** + * @author Mark Pollack + */ +public abstract class ErlangAccessor implements InitializingBean { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + private ConnectionFactory connectionFactory; + + protected OtpConnection createConnection() throws UnknownHostException, OtpAuthException, IOException { + return getConnectionFactory().createConnection(); + } + + public void setConnectionFactory(ConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + } + + public ConnectionFactory getConnectionFactory() { + return this.connectionFactory; + } + + + public void afterPropertiesSet() { + if (getConnectionFactory() == null) { + throw new IllegalArgumentException("Property 'connectionFactory' is required"); + } + } + + protected OtpException convertOtpAccessException(Exception ex) { + return ErlangUtils.convertOtpAccessException(ex); + } +} diff --git a/spring-otp/src/main/java/org/springframework/otp/erlang/support/ErlangUtils.java b/spring-otp/src/main/java/org/springframework/otp/erlang/support/ErlangUtils.java new file mode 100644 index 00000000..daa63835 --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/otp/erlang/support/ErlangUtils.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2010 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.otp.erlang.support; + +import java.io.IOException; + +import org.springframework.otp.erlang.OtpException; +import org.springframework.otp.erlang.OtpIOException; +import org.springframework.otp.erlang.UncategorizedOtpException; +import org.springframework.util.Assert; + +import com.ericsson.otp.erlang.OtpConnection; + +/** + * @author Mark Pollack + */ +public class ErlangUtils { + + /** + * Close the given Connection. + * @param con the Connection to close if necessary + * (if this is null, the call will be ignored) + */ + public static void releaseConnection(OtpConnection con) { + if (con == null) { + return; + } + con.close(); + } + + public static OtpException convertOtpAccessException(Exception ex) { + Assert.notNull(ex, "Exception must not be null"); + if (ex instanceof IOException) { + return new OtpIOException((IOException) ex); + } + //fallback + return new UncategorizedOtpException(ex); + } +} diff --git a/spring-otp/src/main/java/org/springframework/otp/erlang/support/converter/ErlangConversionException.java b/spring-otp/src/main/java/org/springframework/otp/erlang/support/converter/ErlangConversionException.java new file mode 100644 index 00000000..aa5a6eb4 --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/otp/erlang/support/converter/ErlangConversionException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2010 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.otp.erlang.support.converter; + +import org.springframework.otp.erlang.OtpException; + +/** + * @author Mark Pollack + */ +public class ErlangConversionException extends OtpException { + + public ErlangConversionException(String message, Throwable cause) { + super(message, cause); + } + + public ErlangConversionException(String message) { + super(message); + } + +} diff --git a/spring-otp/src/main/java/org/springframework/otp/erlang/support/converter/ErlangConverter.java b/spring-otp/src/main/java/org/springframework/otp/erlang/support/converter/ErlangConverter.java new file mode 100644 index 00000000..9da35f36 --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/otp/erlang/support/converter/ErlangConverter.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2010 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.otp.erlang.support.converter; + +import com.ericsson.otp.erlang.OtpErlangObject; + +/** + * Converter between Java and Erlang Types. Additional support for converting results from RPC calls. + * + * @author Mark Pollack + */ +public interface ErlangConverter { + + /** + * Convert a Java object to a Erlang data type. + * @param object the object to convert + * @return the Erlang data type + * @throws ErlangConversionException in case of conversion failure + */ + OtpErlangObject toErlang(Object object) throws ErlangConversionException; + + /** + * Convert from a Erlang data type to a Java object. + * @param erlangObject the Elang object to convert + * @return the converted Java object + * @throws ErlangConversionException in case of conversion failure + */ + Object fromErlang(OtpErlangObject erlangObject) throws ErlangConversionException; + + Object fromErlangRpc(String module, String function, OtpErlangObject erlangObject) throws ErlangConversionException; +} diff --git a/spring-otp/src/main/java/org/springframework/otp/erlang/support/converter/SimpleErlangConverter.java b/spring-otp/src/main/java/org/springframework/otp/erlang/support/converter/SimpleErlangConverter.java new file mode 100644 index 00000000..312930ae --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/otp/erlang/support/converter/SimpleErlangConverter.java @@ -0,0 +1,143 @@ +/* + * Copyright 2002-2010 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.otp.erlang.support.converter; + +import java.util.ArrayList; + +import com.ericsson.otp.erlang.OtpErlangAtom; +import com.ericsson.otp.erlang.OtpErlangBinary; +import com.ericsson.otp.erlang.OtpErlangBoolean; +import com.ericsson.otp.erlang.OtpErlangByte; +import com.ericsson.otp.erlang.OtpErlangChar; +import com.ericsson.otp.erlang.OtpErlangDouble; +import com.ericsson.otp.erlang.OtpErlangFloat; +import com.ericsson.otp.erlang.OtpErlangInt; +import com.ericsson.otp.erlang.OtpErlangList; +import com.ericsson.otp.erlang.OtpErlangLong; +import com.ericsson.otp.erlang.OtpErlangObject; +import com.ericsson.otp.erlang.OtpErlangRangeException; +import com.ericsson.otp.erlang.OtpErlangShort; +import com.ericsson.otp.erlang.OtpErlangString; + +/** + * Converter that supports the basic types and arrays. + * @author Mark Pollack + * + */ +public class SimpleErlangConverter implements ErlangConverter { + + public Object fromErlang(OtpErlangObject erlangObject) + throws ErlangConversionException { + //TODO support arrays. + return convertErlangToBasicType(erlangObject); + } + + public Object fromErlangRpc(String module, String function, + OtpErlangObject erlangObject) throws ErlangConversionException { + return this.fromErlang(erlangObject); + } + + public OtpErlangObject toErlang(Object obj) + throws ErlangConversionException { + if (obj instanceof OtpErlangObject) { + return (OtpErlangObject) obj; + } + if (obj instanceof Object[]) { + Object[] objectsToConvert = (Object[]) obj; + if (objectsToConvert.length != 0) { + ArrayList tempList = new ArrayList(); + + for (Object objectToConvert : objectsToConvert) { + OtpErlangObject erlangObject = convertBasicTypeToErlang(objectToConvert); + tempList.add(erlangObject); + } + OtpErlangObject ia[] = new OtpErlangObject[tempList.size()]; + return new OtpErlangList(tempList.toArray(ia)); + } else { + return new OtpErlangList(); + } + } else { + return convertBasicTypeToErlang(obj); + } + } + + protected OtpErlangObject convertBasicTypeToErlang(Object obj) { + if (obj instanceof byte[]) { + return new OtpErlangBinary((byte[]) obj); + } else if (obj instanceof Boolean) { + return new OtpErlangBoolean((Boolean) obj); + } else if (obj instanceof Byte) { + return new OtpErlangByte((Byte) obj); + } else if (obj instanceof Character) { + return new OtpErlangChar((Character) obj); + } else if (obj instanceof Double) { + return new OtpErlangDouble((Double) obj); + } else if (obj instanceof Float) { + return new OtpErlangFloat((Float) obj); + } else if (obj instanceof Integer) { + return new OtpErlangInt((Integer) obj); + } else if (obj instanceof Long) { + return new OtpErlangLong((Long) obj); + } else if (obj instanceof Short) { + return new OtpErlangShort((Short) obj); + } else if (obj instanceof String) { + return new OtpErlangString((String) obj); + } else { + throw new ErlangConversionException( + "Could not convert Java object of type [" + obj.getClass() + + "] to an Erlang data type."); + } + } + + protected Object convertErlangToBasicType(OtpErlangObject erlangObject) { + try { + if (erlangObject instanceof OtpErlangBinary) { + return ((OtpErlangBinary) erlangObject).binaryValue(); + } else if (erlangObject instanceof OtpErlangAtom) { + return ((OtpErlangAtom) erlangObject).atomValue(); + } else if (erlangObject instanceof OtpErlangBinary) { + return ((OtpErlangBinary) erlangObject).binaryValue(); + } else if (erlangObject instanceof OtpErlangBoolean) { + return ((OtpErlangBoolean) erlangObject).booleanValue(); + } else if (erlangObject instanceof OtpErlangByte) { + return ((OtpErlangByte) erlangObject).byteValue(); + } else if (erlangObject instanceof OtpErlangChar) { + return ((OtpErlangChar) erlangObject).charValue(); + } else if (erlangObject instanceof OtpErlangDouble) { + return ((OtpErlangDouble) erlangObject).doubleValue(); + } else if (erlangObject instanceof OtpErlangFloat) { + return ((OtpErlangFloat) erlangObject).floatValue(); + } else if (erlangObject instanceof OtpErlangInt) { + return ((OtpErlangInt) erlangObject).intValue(); + } else if (erlangObject instanceof OtpErlangLong) { + return ((OtpErlangLong) erlangObject).longValue(); + } else if (erlangObject instanceof OtpErlangShort) { + return ((OtpErlangShort) erlangObject).shortValue(); + } else if (erlangObject instanceof OtpErlangString) { + return ((OtpErlangString) erlangObject).stringValue(); + } else { + throw new ErlangConversionException( + "Could not convert Erlang object [" + + erlangObject.getClass() + "] to Java type."); + } + } catch (OtpErlangRangeException e) { + throw new ErlangConversionException( + "Could not convert Erlang object [" + + erlangObject.getClass() + "] to Java type.", e); + } + } +} diff --git a/spring-otp/src/main/java/org/springframework/util/exec/Background.java b/spring-otp/src/main/java/org/springframework/util/exec/Background.java new file mode 100644 index 00000000..af5bdda3 --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/util/exec/Background.java @@ -0,0 +1,135 @@ +/* + * Copyright 2002-2010 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.util.exec; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; + +/** + * Class to execute processes in the background. These processes + * will not exit once the controlling Java process exits. + */ +public class Background { + /** + * Execute the command (and its args, ala Runtime.exec), sending the + * output && error streams to the void. + */ + public static void exec(String[] cmd) + throws IOException + { + File devNull; + if(Os.isFamily("unix")) + devNull = new File("/dev/null"); + else if (Os.isFamily("windows")) + devNull = new File("NUL"); + else + throw new IllegalStateException("Unhandled Java environment"); + exec(cmd, devNull, false, devNull, false); + } + + /** + * Execute a command (and its args, ala Runtime.exec) + * + * @param outFile File to send standard out from the process to + * @param appendOut If true, append the file with standard out, + * else truncate or create a new file + * @param errFile File to send standard err from the process to + * @param appendErr If true, append the file with standard error, + * else truncate or create a new file + */ + public static void exec(String[] cmd, + File outFile, boolean appendOut, + File errFile, boolean appendErr) + throws IOException + { + if(Os.isFamily("unix")) + execUnix(cmd, outFile, appendOut, errFile, appendErr); + else if (Os.isFamily("windows")) + execWin(cmd, outFile, appendOut, errFile, appendErr); + else + throw new IllegalStateException("Unhandled Java environment"); + } + + private static void execUnix(String[] cmd, + File outFile, boolean appendOut, + File errFile, boolean appendErr) + throws IOException + { + StringBuffer escaped; + String[] execCmd; + Runtime r; + + escaped = new StringBuffer(); + for(int i=0; i>" : ">") + + Escape.escape(outFile.getAbsolutePath()) + + " 2" + (appendErr == true ? ">>" : " >") + + Escape.escape(errFile.getAbsolutePath()) + + " >" : ">") + + Escape.escape(outFile.getAbsolutePath())); + tmpCmd.add((outFile.equals(errFile) ? " 2&" : " 2") + + (appendErr == true ? ">>" : " >") + + Escape.escape(errFile.getAbsolutePath())); + + Process p = Runtime.getRuntime().exec((String [])tmpCmd.toArray(cmd)); + } + + public static void main(String[] args) throws Exception { + Background.exec(new String[] {"javaq", "foo bar", "bar" }, + new File("garfo"), true, new File("barfo"), true); + } +} diff --git a/spring-otp/src/main/java/org/springframework/util/exec/Escape.java b/spring-otp/src/main/java/org/springframework/util/exec/Escape.java new file mode 100644 index 00000000..b083a2a1 --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/util/exec/Escape.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2010 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.util.exec; + +public class Escape { + private static char[] enlargeArray(char[] in){ + char[] res; + + res = new char[in.length * 2]; + System.arraycopy(in, 0, res, 0, in.length); + return res; + } + + /** + * Escape a string by quoting the magical elements + * (such as whitespace, quotes, slashes, etc.) + */ + public static String escape(String in){ + char[] inChars, outChars, resChars; + int numOut; + + inChars = new char[in.length()]; + outChars = new char[inChars.length]; + in.getChars(0, inChars.length, inChars, 0); + numOut = 0; + + for(int i=0; iPumpStreamHandler for + * stream handling. + */ + public Execute() { + this(new PumpStreamHandler(), null); + } + + + /** + * Creates a new execute object. + * + * @param streamHandler the stream handler used to handle the input and + * output streams of the subprocess. + */ + public Execute(ExecuteStreamHandler streamHandler) { + this(streamHandler, null); + } + + /** + * Creates a new execute object. + * + * @param streamHandler the stream handler used to handle the input and + * output streams of the subprocess. + * @param watchdog a watchdog for the subprocess or null to + * to disable a timeout for the subprocess. + */ + public Execute(ExecuteStreamHandler streamHandler, ExecuteWatchdog watchdog) { + this.streamHandler = streamHandler; + this.watchdog = watchdog; + } + + + /** + * Returns the commandline used to create a subprocess. + * + * @return the commandline used to create a subprocess + */ + public String[] getCommandline() { + return cmdl; + } + + public String getCommandLineString() { + return array2string(getCommandline()); + } + + /** + * Sets the commandline of the subprocess to launch. + * + * @param commandline the commandline of the subprocess to launch + */ + public void setCommandline(String[] commandline) { + cmdl = commandline; + } + + /** + * Set whether to propagate the default environment or not. + * + * @param newenv whether to propagate the process environment. + */ + public void setNewenvironment(boolean newenv) { + newEnvironment = newenv; + } + + /** + * Returns the environment used to create a subprocess. + * + * @return the environment used to create a subprocess + */ + public String[] getEnvironment() { + if (env == null || newEnvironment) return env; + return patchEnvironment(); + } + + + /** + * Sets the environment variables for the subprocess to launch. + * + * @param commandline array of Strings, each element of which has + * an environment variable settings in format key=value + */ + public void setEnvironment(String[] env) { + this.env = env; + } + + /** + * Sets the working directory of the process to execute. + * + *

This is emulated using the antRun scripts unless the OS is + * Windows NT in which case a cmd.exe is spawned, + * or MRJ and setting user.dir works, or JDK 1.3 and there is + * official support in java.lang.Runtime. + * + * @param wd the working directory of the process. + */ + public void setWorkingDirectory(File wd) { + workingDirectory = wd; + } + + // costin + + boolean wait=true; + public void setWait( boolean b ) { + wait=b; + } + + Process process; + /** + * Runs a process defined by the command line and returns its exit status. + * + * @return the exit status of the subprocess or INVALID + * @exception java.io.IOExcpetion The exception is thrown, if launching + * of the subprocess failed + */ + public int execute() throws Exception { + process = + Runtime.getRuntime().exec( getCommandline(), getEnvironment(), + workingDirectory ); + try { + streamHandler.setProcessInputStream(process.getOutputStream()); + streamHandler.setProcessOutputStream(process.getInputStream()); + streamHandler.setProcessErrorStream(process.getErrorStream()); + } catch (IOException e) { + process.destroy(); + throw e; + } + streamHandler.start(); + + if (watchdog != null) watchdog.start(process, + Thread.currentThread()); + + if( log.isTraceEnabled() ) log.trace("Waiting process "); + waitFor(process); + + if( log.isTraceEnabled() ) log.trace("End waiting, stop threads "); + if (watchdog != null) watchdog.stop(); + if( log.isTraceEnabled() ) log.trace("Watchdog stopped "); + streamHandler.stop(); + if( log.isTraceEnabled() ) log.trace("Stream handler stopped "); + if (watchdog != null) { + Exception ex=watchdog.getException(); + if( ex!=null ) + throw ex; + } + int exit= getExitValue(); + + if( log.isDebugEnabled() ) { + log.debug("Done exit=" + exit + " " + getCommandLineString()); + } + return exit; + } + + private String array2string( String sa[]) { + if( sa==null ) return "null"; + StringBuffer sb=new StringBuffer(); + for( int i=0; iExecute to handle input and output stream of + * subprocesses. + * + * @author thomas.haas@softwired-inc.com + */ +public interface ExecuteStreamHandler { + + /** + * Install a handler for the input stream of the subprocess. + * + * @param os output stream to write to the standard input stream of the + * subprocess + */ + void setProcessInputStream(OutputStream os) throws IOException; + + /** + * Install a handler for the error stream of the subprocess. + * + * @param is input stream to read from the error stream from the subprocess + */ + void setProcessErrorStream(InputStream is) throws IOException; + + /** + * Install a handler for the output stream of the subprocess. + * + * @param is input stream to read from the error stream from the subprocess + */ + void setProcessOutputStream(InputStream is) throws IOException; + + /** + * Start handling of the streams. + */ + void start() throws IOException; + + /** + * Stop handling of the streams - will not be restarted. + */ + void stop(); +} diff --git a/spring-otp/src/main/java/org/springframework/util/exec/ExecuteWatchdog.java b/spring-otp/src/main/java/org/springframework/util/exec/ExecuteWatchdog.java new file mode 100644 index 00000000..40be8e2b --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/util/exec/ExecuteWatchdog.java @@ -0,0 +1,194 @@ +/* + * Copyright 2002-2010 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.util.exec; + + +/** + * Destroys a process running for too long. + * For example: + *

+ * ExecuteWatchdog watchdog = new ExecuteWatchdog(30000);
+ * Execute exec = new Execute(myloghandler, watchdog);
+ * exec.setCommandLine(mycmdline);
+ * int exitvalue = exec.execute();
+ * if (exitvalue != SUCCESS && watchdog.killedProcess()){
+ *              // it was killed on purpose by the watchdog
+ * }
+ * 
+ + * @author thomas.haas@softwired-inc.com + * @author Stephane Bailliez + * @see Execute + */ +public class ExecuteWatchdog implements Runnable { + + /** the process to execute and watch for duration */ + private Process process; + + /** timeout duration. Once the process running time exceeds this it should be killed */ + private int timeout; + + /** say whether or not the watchog is currently monitoring a process */ + private boolean watch = false; + + /** exception that might be thrown during the process execution */ + private Exception caught = null; + + /** say whether or not the process was killed due to running overtime */ + private boolean killedProcess = false; + + Thread execThread; + private boolean dontkill=false; + /** + * Creates a new watchdog with a given timeout. + * + * @param timeout the timeout for the process in milliseconds. It must be greather than 0. + */ + public ExecuteWatchdog(int timeout) { + if (timeout < 1) { + throw new IllegalArgumentException("timeout lesser than 1."); + } + this.timeout = timeout; + } + + + public void setDontkill( boolean b ) { + dontkill=b; + } + + /** + * Watches the given process and terminates it, if it runs for too long. + * All information from the previous run are reset. + * @param process the process to monitor. It cannot be null + * @throws IllegalStateException thrown if a process is still being monitored. + */ + public synchronized void start(Process process, Thread execThread) { + if (process == null) { + throw new NullPointerException("process is null."); + } + if (this.process != null) { + throw new IllegalStateException("Already running."); + } + this.caught = null; + this.killedProcess = false; + this.watch = true; + this.process = process; + final Thread thread = new Thread(this, "WATCHDOG"); + this.execThread=execThread; + thread.setDaemon(true); + thread.start(); + } + + /** + * Stops the watcher. It will notify all threads possibly waiting on this object. + */ + public synchronized void stop() { + watch = false; + notifyAll(); + } + + + /** + * Watches the process and terminates it, if it runs for to long. + */ + public synchronized void run() { + try { + // This isn't a Task, don't have a Project object to log. + // project.log("ExecuteWatchdog: timeout = "+timeout+" msec", Project.MSG_VERBOSE); + final long until = System.currentTimeMillis() + timeout; + long now; + while (watch && until > (now = System.currentTimeMillis())) { + try { + wait(until - now); + } catch (InterruptedException e) {} + } + + // if we are here, either someone stopped the watchdog, + // we are on timeout and the process must be killed, or + // we are on timeout and the process has already stopped. + try { + // We must check if the process was not stopped + // before being here + process.exitValue(); + } catch (IllegalThreadStateException e){ + // the process is not terminated, if this is really + // a timeout and not a manual stop then kill it. + //System.out.println("ExecuteWatchdog: timeout"); + if (watch){ + killedProcess = true; + if( ! dontkill ) { + //System.out.println("ExecuteWatchdog: destroying process"); + process.destroy(); + } + if( execThread != null ) { + execThread.interrupt(); + } + } + } + } catch(Exception e) { + caught = e; + } finally { + cleanUp(); + } + //System.out.println("ExecuteWatchdog: done"); + } + + + /** + * reset the monitor flag and the process. + */ + protected void cleanUp() { + watch = false; + process = null; + } + + /** + * This method will rethrow the exception that was possibly caught during the + * run of the process. It will only remains valid once the process has been + * terminated either by 'error', timeout or manual intervention. Information + * will be discarded once a new process is ran. + * @throws BuildException a wrapped exception over the one that was silently + * swallowed and stored during the process run. + */ +// public void checkException() throws BuildException { +// if (caught != null) { +// throw new BuildException("Exception in ExecuteWatchdog.run: " +// + caught.getMessage(), caught); +// } +// } + + public Exception getException() { + return caught; + } + + /** + * Indicates whether or not the watchdog is still monitoring the process. + * @return true if the process is still running, otherwise false. + */ + public boolean isWatching(){ + return watch; + } + + /** + * Indicates whether the last process run was killed on timeout or not. + * @return true if the process was killed otherwise false. + */ + public boolean killedProcess(){ + return killedProcess; + } +} + diff --git a/spring-otp/src/main/java/org/springframework/util/exec/Os.java b/spring-otp/src/main/java/org/springframework/util/exec/Os.java new file mode 100644 index 00000000..111d4abc --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/util/exec/Os.java @@ -0,0 +1,140 @@ +/* + * Copyright 2002-2010 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.util.exec; + +import java.util.Locale; + +/** + * Condition that tests the OS type. + * + * @author Stefan Bodewig + * @author Magesh Umasankar + */ +public class Os { + private static final String osName = + System.getProperty("os.name").toLowerCase(Locale.US); + private static final String osArch = + System.getProperty("os.arch").toLowerCase(Locale.US); + private static final String osVersion = + System.getProperty("os.version").toLowerCase(Locale.US); + private static final String pathSep = System.getProperty("path.separator"); + + /** + * Determines if the OS on which Ant is executing matches the + * given OS family. + * + * @param f The OS family type desired
+ * Possible values:
+ *
  • dos
  • + *
  • mac
  • + *
  • netware
  • + *
  • os/2
  • + *
  • unix
  • + *
  • windows
+ * @since 1.5 + */ + public static boolean isFamily(String family) { + return isOs(family, null, null, null); + } + + /** + * Determines if the OS on which Ant is executing matches the + * given OS name. + * + * @since 1.7 + */ + public static boolean isName(String name) { + return isOs(null, name, null, null); + } + + /** + * Determines if the OS on which Ant is executing matches the + * given OS architecture. + * + * @since 1.7 + */ + public static boolean isArch(String arch) { + return isOs(null, null, arch, null); + } + + /** + * Determines if the OS on which Ant is executing matches the + * given OS version. + * + * @since 1.7 + */ + public static boolean isVersion(String version) { + return isOs(null, null, null, version); + } + + /** + * Determines if the OS on which Ant is executing matches the + * given OS family, name, architecture and version + * + * @param family The OS family + * @param name The OS name + * @param arch The OS architecture + * @param version The OS version + * + * @since 1.7 + */ + public static boolean isOs(String family, String name, String arch, + String version) { + boolean retValue = false; + + if (family != null || name != null || arch != null + || version != null) { + + boolean isFamily = true; + boolean isName = true; + boolean isArch = true; + boolean isVersion = true; + + if (family != null) { + if (family.equals("windows")) { + isFamily = osName.indexOf("windows") > -1; + } else if (family.equals("os/2")) { + isFamily = osName.indexOf("os/2") > -1; + } else if (family.equals("netware")) { + isFamily = osName.indexOf("netware") > -1; + } else if (family.equals("dos")) { + isFamily = pathSep.equals(";") && !isFamily("netware"); + } else if (family.equals("mac")) { + isFamily = osName.indexOf("mac") > -1; + } else if (family.equals("unix")) { + isFamily = pathSep.equals(":") + && (!isFamily("mac") || osName.endsWith("x")); + } else { + throw new RuntimeException( + "Don\'t know how to detect os family \"" + + family + "\""); + } + } + if (name != null) { + isName = name.equals(osName); + } + if (arch != null) { + isArch = arch.equals(osArch); + } + if (version != null) { + isVersion = version.equals(osVersion); + } + retValue = isFamily && isName && isArch && isVersion; + } + return retValue; + } +} diff --git a/spring-otp/src/main/java/org/springframework/util/exec/PumpStreamHandler.java b/spring-otp/src/main/java/org/springframework/util/exec/PumpStreamHandler.java new file mode 100644 index 00000000..478c5fc9 --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/util/exec/PumpStreamHandler.java @@ -0,0 +1,125 @@ +/* + * Copyright 2002-2010 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.util.exec; + +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Copies standard output and error of subprocesses to standard output and + * error of the parent process. + * + * TODO: standard input of the subprocess is not implemented. + * + * @author thomas.haas@softwired-inc.com + */ +public class PumpStreamHandler implements ExecuteStreamHandler { + + private Thread inputThread; + private Thread errorThread; + + private OutputStream out, err; + boolean running=false; + + public PumpStreamHandler(OutputStream out, OutputStream err) { + this.out = out; + this.err = err; + } + + public PumpStreamHandler(OutputStream outAndErr) { + this(outAndErr, outAndErr); + } + + public PumpStreamHandler() { + this(System.out, System.err); + } + + public void setProcessOutputStream(InputStream is) { + createProcessOutputPump(is, out); + } + + + public void setProcessErrorStream(InputStream is) { + createProcessErrorPump(is, err); + } + + + public void setProcessInputStream(OutputStream os) { + } + + + public void start() { + inputThread.start(); + errorThread.start(); + running=true; + } + + + public void stop() { + if( ! running ) return; + try { + //if( log.isDebugEnabled() ) log.debug("Joining it"); + // inputThread.interrupt(); + inputThread.join(1000); + //if( log.isDebugEnabled() ) log.debug("Joined" ); + } catch(InterruptedException e) {} + try { + //if( log.isDebugEnabled() ) log.debug("Joining it"); + // errorThread.interrupt(); + errorThread.join(1000); + //if( log.isDebugEnabled() ) log.debug("Joined" ); + } catch(InterruptedException e) {} + try { + err.flush(); + } catch (IOException e) {} + try { + out.flush(); + } catch (IOException e) {} + running=false; + } + + protected OutputStream getErr() { + return err; + } + + protected OutputStream getOut() { + return out; + } + + protected void createProcessOutputPump(InputStream is, OutputStream os) { + inputThread = createPump(is, os); + } + + protected void createProcessErrorPump(InputStream is, OutputStream os) { + errorThread = createPump(is, os); + } + + + /** + * Creates a stream pumper to copy the given input stream to the given output stream. + */ + protected Thread createPump(InputStream is, OutputStream os) { + final Thread result = new Thread(new StreamPumper(is, os)); + result.setDaemon(true); + return result; + } + + private static org.apache.commons.logging.Log log= + org.apache.commons.logging.LogFactory.getLog( PumpStreamHandler.class ); + +} diff --git a/spring-otp/src/main/java/org/springframework/util/exec/StreamPumper.java b/spring-otp/src/main/java/org/springframework/util/exec/StreamPumper.java new file mode 100644 index 00000000..b0c32654 --- /dev/null +++ b/spring-otp/src/main/java/org/springframework/util/exec/StreamPumper.java @@ -0,0 +1,69 @@ +/* + * Copyright 2002-2010 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.util.exec; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Copies all data from an input stream to an output stream. + * + * @author thomas.haas@softwired-inc.com + */ +public class StreamPumper implements Runnable { + + // TODO: make SIZE and SLEEP instance variables. + // TODO: add a status flag to note if an error occured in run. + + private final static int SLEEP = 5; + private final static int SIZE = 128; + private InputStream is; + private OutputStream os; + + + /** + * Create a new stream pumper. + * + * @param is input stream to read data from + * @param os output stream to write data to. + */ + public StreamPumper(InputStream is, OutputStream os) { + this.is = is; + this.os = os; + } + + + /** + * Copies data from the input stream to the output stream. + * + * Terminates as soon as the input stream is closed or an error occurs. + */ + public void run() { + final byte[] buf = new byte[SIZE]; + + int length; + try { + while ((length = is.read(buf)) > 0) { + os.write(buf, 0, length); + try { + Thread.sleep(SLEEP); + } catch (InterruptedException e) {} + } + } catch(IOException e) {} + } +} diff --git a/spring-otp/template.mf b/spring-otp/template.mf new file mode 100644 index 00000000..3154a3f3 --- /dev/null +++ b/spring-otp/template.mf @@ -0,0 +1,8 @@ +Bundle-SymbolicName: org.springframework.otp +Bundle-Name: Spring OTP Support +Bundle-Vendor: SpringSource +Bundle-ManifestVersion: 2 +Import-Template: + org.springframework.*;version="[3.0.3, 4.0.0)", + org.apache.commons.logging;version="[1.1.1, 2.0.0)", + com.ericsson.otp.erlang;version="0" diff --git a/spring-rabbit-admin/.classpath b/spring-rabbit-admin/.classpath new file mode 100644 index 00000000..7a0eedb4 --- /dev/null +++ b/spring-rabbit-admin/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/spring-rabbit-admin/.project b/spring-rabbit-admin/.project new file mode 100644 index 00000000..df9ae64f --- /dev/null +++ b/spring-rabbit-admin/.project @@ -0,0 +1,35 @@ + + + spring-rabbit-admin + + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.maven.ide.eclipse.maven2Builder + + + + + org.springframework.ide.eclipse.core.springbuilder + + + + + + org.springframework.ide.eclipse.core.springnature + org.maven.ide.eclipse.maven2Nature + org.eclipse.jdt.core.javanature + org.eclipse.wst.common.project.facet.core.nature + + diff --git a/spring-rabbit-admin/.settings/org.maven.ide.eclipse.prefs b/spring-rabbit-admin/.settings/org.maven.ide.eclipse.prefs new file mode 100644 index 00000000..3b616447 --- /dev/null +++ b/spring-rabbit-admin/.settings/org.maven.ide.eclipse.prefs @@ -0,0 +1,9 @@ +#Tue Jun 29 21:59:01 EDT 2010 +activeProfiles= +eclipse.preferences.version=1 +fullBuildGoals=process-test-resources +includeModules=false +resolveWorkspaceProjects=true +resourceFilterGoals=process-resources resources\:testResources +skipCompilerPlugin=true +version=1 diff --git a/spring-rabbit-admin/.springBeans b/spring-rabbit-admin/.springBeans new file mode 100644 index 00000000..dbe284d5 --- /dev/null +++ b/spring-rabbit-admin/.springBeans @@ -0,0 +1,13 @@ + + + 1 + + + + + + + + + + diff --git a/spring-rabbit-admin/pom.xml b/spring-rabbit-admin/pom.xml new file mode 100644 index 00000000..2a8f8771 --- /dev/null +++ b/spring-rabbit-admin/pom.xml @@ -0,0 +1,126 @@ + + 4.0.0 + + org.springframework.amqp + spring-amqp-parent + 1.0.0.BUILD-SNAPSHOT + ../spring-amqp-parent/pom.xml + + spring-rabbit-admin + jar + Spring RabbitMQ Administrative Support + + + com.rabbitmq + amqp-client + + + org.springframework.amqp + spring-rabbit + + + org.springframework.amqp + spring-otp + + + org.springframework.amqp + spring-amqp-core + + + + + org.springframework + spring-aop + + + org.springframework + spring-oxm + + + org.springframework + spring-tx + + + org.springframework + spring-test + + + + + commons-io + commons-io + + + + + org.slf4j + slf4j-api + + + org.slf4j + jcl-over-slf4j + runtime + + + org.slf4j + slf4j-log4j12 + runtime + + + log4j + log4j + + + javax.mail + mail + + + javax.jms + jms + + + com.sun.jdmk + jmxtools + + + com.sun.jmx + jmxri + + + runtime + + + + javax.annotation + jsr250-api + true + + + + cglib + cglib-nodep + true + + + + org.mockito + mockito-all + test + + + + junit + junit + + + + + + + com.springsource.bundlor + com.springsource.bundlor.maven + + + + diff --git a/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/admin/RabbitAdminOperations.java b/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/admin/RabbitAdminOperations.java new file mode 100644 index 00000000..6cd4caf2 --- /dev/null +++ b/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/admin/RabbitAdminOperations.java @@ -0,0 +1,166 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.admin; + +/** + * Performs broker administration. Uses {@link Queue}, {@link org.springframework.amqp.rabbit.config.Exchange}, + * and {@link Binding} as conveniences for code based configuration in Spring. + */ +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import org.springframework.amqp.core.AbstractExchange; +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.Queue; + +import com.rabbitmq.client.AMQP; + +/** + * Performs administration tasks for broker administration. + *

Goal is to support full CRUD of Exchages, Queues, Bindings, User, VHosts, etc. + *

Current implementations expose operations with basic typics via JMX. + * + * @author Mark Pollack + * + */ +public interface RabbitAdminOperations { + + //TODO the method naming is opposite to that in the channel interface...switch to Channel API style? + + // Declaration/Creation + + AMQP.Queue.DeclareOk declareQueue(Queue queue); + + AMQP.Exchange.DeclareOk declareExchange(AbstractExchange exchange); + + AMQP.Queue.BindOk declareBinding(Binding binding); + + + + // Can be used with JMX as the expose simple arguments. + + // Deletion + + AMQP.Queue.DeleteOk deleteQueue(String queueName); + + AMQP.Queue.DeleteOk deleteQueue(String queueName, boolean ifUnused, boolean ifEmpty); + + AMQP.Exchange.DeleteOk deleteExchange(String exchange); + + AMQP.Exchange.DeleteOk deleteExchange(String exchange, boolean ifUnused); + + + // Purge + + AMQP.Queue.PurgeOk purgeQueue(String queueName); + + AMQP.Queue.PurgeOk purgeQueue(String queueName, boolean nowait); + + // Unbind + + AMQP.Queue.UnbindOk unbindQueue(String queue, String exchange, String routingKey); + + AMQP.Queue.UnbindOk unbindQueue(String queue, String exchange, String routingKey, Map arguments); + + // Message Delivery + + void recoverAsync(boolean requeue); + + // Misc + + void addUser(String username, String password); + + void deleteUser(String username); + + void changeUserPassword(String username, String newPassword); + + List listUsers(); + + int addVhost(String vhostPath); + + int deleteVhost(String vhostPath); + + // TODO permissions + + void setPermissions(String username, Pattern configure, Pattern read, Pattern write); + + void setPermissions(String username, Pattern configure, Pattern read, Pattern write, String vhostPath); + + void clearPermissions(String username); + + void clearPermissions(String username, String vhostPath); + + List listPermissions(); + + List listPermissions(String vhostPath); + + List listUserPermissions(String username); + + + // Start/Stop/Re + + /** + * Starts the RabbitMQ application on an already running node. This command is typically run after performing other + * management actions that required the RabbitMQ application to be stopped, e.g. reset. + */ + void startBrokerApplication(); + + /** + * Stops the RabbitMQ application, leaving the Erlang node running. + */ + void stopBrokerApplication(); + + /** + * Starts the Erlang node where RabbitMQ is running by shelling out to the directory specified by RABBIT_HOME and + * executing the standard named start script. It spawns the shell command execution into its own thread. + */ + void startNode(); + + /** + * Stops the halts the Erlang node on which RabbitMQ is running. To restart the node you will need to execute + * the start script from a command line or via other means. + */ + void stopNode(); + + /** + * Removes the node from any cluster it belongs to, removes all data from the management database, + * such as configured users and vhosts, and deletes all persistent messages. + *

+ * For {@link resetNode} and {@link forceReset} to succeed the RabbitMQ application must have + * been stopped, e.g. {@link stopBroker} + */ + void resetNode(); + + /** + * The forceResetNode command differs from {@link resetNode()} in that it resets the node unconditionally, + * regardless of the current management database state and cluster configuration. It should only be + * used as a last resort if the database or cluster configuration has been corrupted. + *

+ * For {@link resetNode} and {@link forceReset} to succeed the RabbitMQ application must have + * been stopped, e.g. {@link stopBroker} + */ + void forceResetNode(); + + /** + * Returns the status of the node. + * @return status of the node. + */ + RabbitStatus getStatus(); + + +} diff --git a/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/admin/RabbitAdminTemplate.java b/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/admin/RabbitAdminTemplate.java new file mode 100644 index 00000000..b039b832 --- /dev/null +++ b/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/admin/RabbitAdminTemplate.java @@ -0,0 +1,385 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.admin; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.ChannelCallback; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.jmx.export.annotation.ManagedOperation; +import org.springframework.jmx.export.annotation.ManagedOperationParameters; +import org.springframework.jmx.export.annotation.ManagedOperationParameter; +import org.springframework.jmx.export.annotation.ManagedResource; +import org.springframework.otp.erlang.connection.SimpleConnectionFactory; +import org.springframework.otp.erlang.core.ErlangTemplate; +import org.springframework.util.Assert; +import org.springframework.util.exec.Execute; +import org.springframework.util.exec.Os; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.AMQP.Exchange.DeleteOk; +import com.rabbitmq.client.AMQP.Queue.PurgeOk; +import com.rabbitmq.client.AMQP.Queue.UnbindOk; + +/** + * Performs broker administration. Uses {@link Queue}, {@link org.springframework.amqp.core.AbstractExchange}, + * and {@link Binding} as conveniences for code based configuration in Spring. + * + * Exposes administrative operations from Channel and rabbitmqctl through JMX. + * + * @author Mark Pollack + * @author Mark Fisher + */ +@ManagedResource +public class RabbitAdminTemplate implements RabbitAdminOperations { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + private RabbitTemplate rabbitTemplate; + + private ErlangTemplate erlangTemplate; + + public RabbitAdminTemplate(ConnectionFactory rabbitConnectionFactory) { + this.rabbitTemplate = new RabbitTemplate(rabbitConnectionFactory); + initializeDefaultErlangTemplate(rabbitTemplate); + } + + public RabbitAdminTemplate(ConnectionFactory rabbitConnectionFactory, + org.springframework.otp.erlang.connection.ConnectionFactory otpConnectionFactory) + { + this.rabbitTemplate = new RabbitTemplate(rabbitConnectionFactory); + createErlangTemplate(otpConnectionFactory); + } + + public RabbitAdminTemplate(RabbitTemplate rabbitTemplate) { + this.rabbitTemplate = rabbitTemplate; + initializeDefaultErlangTemplate(rabbitTemplate); + } + +/* + public RabbitAdminTemplate(RabbitTemplate rabbitTemplate, ErlangTemplate erlangTemplate) { + this.rabbitTemplate = rabbitTemplate; + this.erlangTemplate = erlangTemplate; + } + */ + + public AMQP.Queue.DeclareOk declareQueue() { + return rabbitTemplate.execute(new ChannelCallback() { + public AMQP.Queue.DeclareOk doInRabbit(Channel channel) throws Exception { + AMQP.Queue.DeclareOk result = channel.queueDeclare(); + System.out.println("created queue: " + result.getQueue()); + return result; + } + }); + } + + public AMQP.Queue.DeclareOk declareQueue(final Queue queue) { + Assert.notNull(queue.getName(), "Queue must contain a name. " + + "For a server-generated name, call the declareQueue() method that takes no argument."); + return rabbitTemplate.execute(new ChannelCallback() { + public AMQP.Queue.DeclareOk doInRabbit(Channel channel) throws Exception { + logger.debug("Declaring queue [" + queue.getName() + "]"); + return channel.queueDeclare(queue.getName(), queue.isPassive(), queue.isDurable(), + queue.isExclusive(), queue.isAutoDelete(), queue.getArguments()); + } + }); + } + + public AMQP.Exchange.DeclareOk declareExchange(final org.springframework.amqp.core.AbstractExchange exchange) { + return rabbitTemplate.execute(new ChannelCallback() { + public AMQP.Exchange.DeclareOk doInRabbit(Channel channel) throws Exception { + logger.debug("Declaring exchange [" + exchange.getName() + "]"); + return channel.exchangeDeclare(exchange.getName(), exchange.getExchangeType().name(), + exchange.isPassive(), + exchange.isDurable(), + exchange.isAutoDelete(), + exchange.getArguments()); + } + }); + + } + + public AMQP.Queue.BindOk declareBinding(final Binding binding) { + return rabbitTemplate.execute(new ChannelCallback() { + + public AMQP.Queue.BindOk doInRabbit(Channel channel) throws Exception { + + logger.debug("Binding queue to exchange with routing key"); + return channel.queueBind(binding.getQueue(), binding.getExchange(), binding.getRoutingKey(), binding.getArguments()); + } + }); + + } + + public ErlangTemplate getErlangTemplate() { + return this.erlangTemplate; + } + + protected void initializeDefaultErlangTemplate(RabbitTemplate rabbitTemplate) { + String peerNodeName = "rabbit@" + rabbitTemplate.getConnectionFactory().getHostName(); + logger.debug("Creating jinterface connection with peerNodeName = [" + peerNodeName); + SimpleConnectionFactory otpCf = new SimpleConnectionFactory("rabbit-spring-monitor", peerNodeName); + otpCf.afterPropertiesSet(); + createErlangTemplate(otpCf); + } + + private void createErlangTemplate(org.springframework.otp.erlang.connection.ConnectionFactory otpCf) { + erlangTemplate = new ErlangTemplate(otpCf); + erlangTemplate.setErlangConverter(new RabbitControlErlangConverter()); + erlangTemplate.afterPropertiesSet(); + } + + + @ManagedOperation(description="Delete a exchange, without regard for whether it is in use or has messages on it") + @ManagedOperationParameters(@ManagedOperationParameter(name = "exchange", description = "the name of the exchange")) + public DeleteOk deleteExchange(String exchange) { + // TODO Auto-generated method stub + return null; + } + + @ManagedOperation() + public DeleteOk deleteExchange(String exchange, boolean ifUnused) { + // TODO Auto-generated method stub + return null; + } + + @ManagedOperation() + public com.rabbitmq.client.AMQP.Queue.DeleteOk deleteQueue(String queueName) { + // TODO Auto-generated method stub + return null; + } + + @ManagedOperation() + public com.rabbitmq.client.AMQP.Queue.DeleteOk deleteQueue( + String queueName, boolean ifUnused, boolean ifEmpty) { + // TODO Auto-generated method stub + return null; + } + + @ManagedOperation() + public PurgeOk purgeQueue(String queueName) { + // TODO Auto-generated method stub + return null; + } + + @ManagedOperation() + public PurgeOk purgeQueue(String queueName, boolean nowait) { + // TODO Auto-generated method stub + return null; + } + + @ManagedOperation() + public void recoverAsync(boolean requeue) { + // TODO Auto-generated method stub + + } + + @ManagedOperation() + public UnbindOk unbindQueue(String queue, String exchange, String routingKey) { + // TODO Auto-generated method stub + return null; + } + + //TODO will Hyperic allow invocation with a Map. If not, remove from @ManagedOperation + + @ManagedOperation() + public UnbindOk unbindQueue(String queue, String exchange, + String routingKey, Map arguments) { + // TODO Auto-generated method stub + return null; + } + + @ManagedOperation() + public void addUser(String username, String password) { + erlangTemplate.executeAndConvertRpc("rabbit_access_control", "add_user", username.getBytes(), password.getBytes()); + } + + + @ManagedOperation() + public void deleteUser(String username) { + erlangTemplate.executeAndConvertRpc("rabbit_access_control", "delete_user", username.getBytes()); + } + + @SuppressWarnings("unchecked") + public List listUsers() { + return (List) erlangTemplate.executeAndConvertRpc("rabbit_access_control", "list_users"); + } + + public Map getQueueInfo(String name) { + // TODO: is there a more efficient way (direct RPC) to retrieve info for a single queue by name? + Map> queues = this.getQueueInfo(); + return queues.get(name); + } + + @SuppressWarnings("unchecked") + public Map> getQueueInfo() { // TODO: pass virtual host?... currently hardcoded to default ("/") + return (Map>) erlangTemplate.executeAndConvertRpc("rabbit_amqqueue", "info_all", "/".getBytes()); + } + + @ManagedOperation() + public void changeUserPassword(String username, String newPassword) { + erlangTemplate.executeAndConvertRpc("rabbit_access_control", "change_password", username.getBytes(), newPassword.getBytes()); + } + + @ManagedOperation() + public void startBrokerApplication() { + logger.debug("Starting Rabbit Application."); + erlangTemplate.executeAndConvertRpc("rabbit", "start"); + + } + + @ManagedOperation() + public void stopBrokerApplication() { + logger.debug("Stopping Rabbit Application."); + erlangTemplate.executeAndConvertRpc("rabbit", "stop"); + } + + @ManagedOperation() + public void startNode() { + logger.debug("Staring RabbitMQ node by shelling out command line."); + final Execute execute = new Execute(); + + String rabbitStartCommand = null; + if (Os.isFamily("windows")) { + String rabbitHome = System.getenv("RABBITMQ_HOME"); + // TODO remove any trailing directory separators on rabbit home var. + if (rabbitHome == null) { + throw new IllegalArgumentException( + "RABBITMQ_HOME environment variable not set."); + } + rabbitStartCommand = rabbitHome + + System.getProperty("file.separator") + "sbin" + + System.getProperty("file.separator") + + "rabbitmq-server.bat"; + } else { + // TODO abstract out install location and Win/Unix shell script name differencese + throw new IllegalArgumentException("Only support for windows OS family at the moment..."); + } + + if (rabbitStartCommand != null) { + execute.setCommandline(new String[] { rabbitStartCommand }); + } else { + throw new IllegalArgumentException( + "Could determine OS to create rabbit start command"); + } + + SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor(); + executor.execute(new Runnable() { + public void run() { + try { + execute.execute(); + } catch (Exception e) { + logger.error("Could start node", e); + } + } + }); + } + + + @ManagedOperation() + public void stopNode() { + logger.debug("Stopping RabbitMQ node."); + erlangTemplate.executeAndConvertRpc("rabbit", "stop_and_halt"); + } + + @ManagedOperation() + public void resetNode() { + erlangTemplate.executeAndConvertRpc("rabbit_mnesia", "reset"); + } + + @ManagedOperation() + public void forceResetNode() { + erlangTemplate.executeAndConvertRpc("rabbit_mnesia", "force_reset"); + } + + @ManagedOperation() + public RabbitStatus getStatus() { + return (RabbitStatus) getErlangTemplate().executeAndConvertRpc("rabbit", "status"); + } + + + @ManagedOperation() + public int addVhost(String vhostPath) { + // TODO Auto-generated method stub + return 0; + } + + @ManagedOperation() + public int deleteVhost(String vhostPath) { + // TODO Auto-generated method stub + return 0; + } + + // Permissions + + public void clearPermissions(String username, String vhostPath) { + // TODO Auto-generated method stub + + } + + + public void clearPermissions(String username) { + // TODO Auto-generated method stub + + } + + + public List listPermissions() { + // TODO Auto-generated method stub + return null; + } + + + public List listPermissions(String vhostPath) { + // TODO Auto-generated method stub + return null; + } + + + public List listUserPermissions(String username) { + // TODO Auto-generated method stub + return null; + } + + + public void setPermissions(String username, Pattern configure, + Pattern read, Pattern write, String vhostPath) { + // TODO Auto-generated method stub + + } + + + public void setPermissions(String username, Pattern configure, + Pattern read, Pattern write) { + // TODO Auto-generated method stub + + } + + + + +} diff --git a/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/admin/RabbitControlErlangConverter.java b/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/admin/RabbitControlErlangConverter.java new file mode 100644 index 00000000..bb0c8034 --- /dev/null +++ b/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/admin/RabbitControlErlangConverter.java @@ -0,0 +1,192 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.admin; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.otp.erlang.core.Application; +import org.springframework.otp.erlang.core.Node; +import org.springframework.otp.erlang.support.converter.ErlangConversionException; +import org.springframework.otp.erlang.support.converter.ErlangConverter; +import org.springframework.otp.erlang.support.converter.SimpleErlangConverter; + +import com.ericsson.otp.erlang.OtpErlangBinary; +import com.ericsson.otp.erlang.OtpErlangList; +import com.ericsson.otp.erlang.OtpErlangObject; +import com.ericsson.otp.erlang.OtpErlangTuple; + +/*** + * Converter that understands the responses from the rabbit control module and related functionality. + * + * @author Mark Pollack + * @author Mark Fisher + */ +public class RabbitControlErlangConverter extends SimpleErlangConverter implements ErlangConverter { + + + protected final Log logger = LogFactory.getLog(getClass()); + + + private Map converterMap = new HashMap(); + + + public RabbitControlErlangConverter() { + initializeConverterMap(); + } + + + public Object fromErlangRpc(String module, String function, OtpErlangObject erlangObject) throws ErlangConversionException { + ErlangConverter converter = getConverter(module, function); + if (converter != null) { + return converter.fromErlang(erlangObject); + } else { + return super.fromErlangRpc(module, function, erlangObject); + } + } + + + protected ErlangConverter getConverter(String module, String function) { + return converterMap.get(generateKey(module, function)); + } + + + protected void initializeConverterMap() { + registerConverter("rabbit_access_control", "list_users", new ListUsersConverter()); + registerConverter("rabbit", "status", new StatusConverter()); + registerConverter("rabbit_amqqueue", "info_all", new InfoAllConverter()); + } + + protected void registerConverter(String module, String function, ErlangConverter listUsersConverter) { + converterMap.put(generateKey(module, function), listUsersConverter); + } + + protected String generateKey(String module, String function) { + return module + "%" + function; + } + + public class ListUsersConverter extends SimpleErlangConverter { + + public Object fromErlang(OtpErlangObject erlangObject) throws ErlangConversionException { + + List users = new ArrayList(); + if (erlangObject instanceof OtpErlangList) { + OtpErlangList erlangList = (OtpErlangList) erlangObject; + for (OtpErlangObject obj : erlangList) { + if (obj instanceof OtpErlangBinary) { + OtpErlangBinary binary = (OtpErlangBinary) obj; + users.add(new String(binary.binaryValue())); + } + } + } + return users; + } + } + + public class StatusConverter extends SimpleErlangConverter { + + public Object fromErlang(OtpErlangObject erlangObject) throws ErlangConversionException { + + List applications = new ArrayList(); + List nodes = new ArrayList(); + List runningNodes = new ArrayList(); + if (erlangObject instanceof OtpErlangList) { + OtpErlangList erlangList = (OtpErlangList) erlangObject; + + OtpErlangTuple runningAppTuple = (OtpErlangTuple)erlangList.elementAt(0); + OtpErlangList appList = (OtpErlangList)runningAppTuple.elementAt(1); + extractApplications(applications, appList); + + OtpErlangTuple nodesTuple = (OtpErlangTuple)erlangList.elementAt(1); + OtpErlangList nodesList = (OtpErlangList)nodesTuple.elementAt(1); + extractNodes(nodes, nodesList); + + + OtpErlangTuple runningNodesTuple = (OtpErlangTuple)erlangList.elementAt(2); + nodesList = (OtpErlangList)runningNodesTuple.elementAt(1); + extractNodes(runningNodes, nodesList); + + /* + for (OtpErlangObject obj : erlangList) { + if (obj instanceof OtpErlangBinary) { + OtpErlangBinary binary = (OtpErlangBinary) obj; + users.add(new String(binary.binaryValue())); + } + }*/ + } + + return new RabbitStatus(applications, nodes, runningNodes); + } + + private void extractNodes(List nodes, OtpErlangList nodesList) { + for (OtpErlangObject erlangNodeName : nodesList) { + String nodeName = erlangNodeName.toString(); + nodes.add(new Node(nodeName)); + } + } + + private void extractApplications(List applications, OtpErlangList appList) { + for (OtpErlangObject appDescription : appList) { + OtpErlangTuple appDescriptionTuple = (OtpErlangTuple)appDescription; + String name = appDescriptionTuple.elementAt(0).toString(); + String description = appDescriptionTuple.elementAt(1).toString(); + String version = appDescriptionTuple.elementAt(2).toString(); + applications.add(new Application(name, description, version)); + } + } + } + + public class InfoAllConverter extends SimpleErlangConverter { + + @Override + public Object fromErlang(OtpErlangObject erlangObject) throws ErlangConversionException { + Map> allQueuesMap = new HashMap>(); + if (erlangObject instanceof OtpErlangList) { + OtpErlangList erlangList = (OtpErlangList) erlangObject; + for (OtpErlangObject element : erlangList.elements()) { + String queueName = null; + Map queueMap = new HashMap(); + OtpErlangList itemList = (OtpErlangList) element; + for (OtpErlangObject item : itemList.elements()) { + OtpErlangTuple tuple = (OtpErlangTuple) item; + if (tuple.arity() == 2) { + String key = tuple.elementAt(0).toString(); + Object value = tuple.elementAt(1); + if ("name".equals(key)) { + // arity should be 4 for the 'name' tuple, we want the last element + Object nameElement = ((OtpErlangTuple) value).elementAt(3); + queueName = new String(((OtpErlangBinary) nameElement).binaryValue()); + value = queueName; + } + queueMap.put(tuple.elementAt(0).toString(), value.toString()); + } + } + if (queueMap != null) { + allQueuesMap.put(queueName, queueMap); + } + } + } + return allQueuesMap; + } + } + +} diff --git a/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/admin/RabbitStatus.java b/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/admin/RabbitStatus.java new file mode 100644 index 00000000..6c394d61 --- /dev/null +++ b/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/admin/RabbitStatus.java @@ -0,0 +1,69 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.admin; + +import java.util.List; + +import org.springframework.otp.erlang.core.Application; +import org.springframework.otp.erlang.core.Node; + +/** + * The status object returned from querying the broker via the + * + * @author Mark Pollack + * + */ +public class RabbitStatus { + + private List runningApplications; + + private List nodes; + + private List runningNodes; + + + + public RabbitStatus(List runningApplications, + List nodes, List runningNodes) { + super(); + this.runningApplications = runningApplications; + this.nodes = nodes; + this.runningNodes = runningNodes; + } + + public List getRunningApplications() { + return runningApplications; + } + + public List getNodes() { + return nodes; + } + + public List getRunningNodes() { + return runningNodes; + } + + @Override + public String toString() { + return "RabbitStatus [runningApplications=" + runningApplications + + ", runningNodes=" + runningNodes + ", nodes=" + nodes + "]"; + } + + + + +} diff --git a/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/admin/config/AbstractRabbitConfiguration.java b/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/admin/config/AbstractRabbitConfiguration.java new file mode 100644 index 00000000..3b8e485b --- /dev/null +++ b/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/admin/config/AbstractRabbitConfiguration.java @@ -0,0 +1,108 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.admin.config; + +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.DirectExchange; +import org.springframework.amqp.core.FanoutExchange; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.core.TopicExchange; +import org.springframework.amqp.rabbit.admin.RabbitAdminTemplate; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.rabbitmq.client.AMQP.Queue.DeclareOk; + +/** + * Abstract base class for code based configuration of Spring managed Rabbit broker infrastructure, + * i.e. Queues, Exchanges, Bindings. + *

Subclasses are required to provide an implementation of rabbitTemplate from which the the bean + * 'rabbitAdminTemplate' will be created. + *

There are several convenience 'declare' methods to make the configuration in subclasses + * more terse and readable. + *

Builder classes to make the configuration more fluent are under development. Look in the + * StockApp sample for an example of a fluent API to declare a binding. + * + * @author Mark Pollack + */ +@Configuration +public abstract class AbstractRabbitConfiguration { + + @Bean + public abstract RabbitTemplate rabbitTemplate(); + + @Bean + public RabbitAdminTemplate rabbitAdminTemplate() { + return new RabbitAdminTemplate(rabbitTemplate()); + } + + public Queue declareQueue() { + DeclareOk result = rabbitAdminTemplate().declareQueue(); + Queue queue = new Queue(result.getQueue()); + queue.setExclusive(true); + queue.setAutoDelete(true); + queue.setDurable(false); + return queue; + } + + public DirectExchange defaultDirectExchange() { + return new DirectExchange(""); // server already has declared it. + } + + public Queue declare(Queue queue) { + rabbitAdminTemplate().declareQueue(queue); + return queue; + } + + public Binding declare(Binding binding) { + rabbitAdminTemplate().declareBinding(binding); + return binding; + } + + /** + * Provides the same as {@link declare(Binding)} but is better to use in conjunction with + * BindingBuilder so the fluent API reads better. + *

+ * For example, + * "declareBinding(from(marketDataQueue()).to(marketDataExchange()).with(marketDataRoutingKey));" + * instead of + * "declare(from(marketDataQueue()).to(marketDataExchange()).with(marketDataRoutingKey));" + * @param binding + * @return + */ + public Binding declareBinding(Binding binding) { + rabbitAdminTemplate().declareBinding(binding); + return binding; + } + + public DirectExchange declare(DirectExchange directExchange) { + rabbitAdminTemplate().declareExchange(directExchange); + return directExchange; + } + + public TopicExchange declare(TopicExchange topicExchange) { + rabbitAdminTemplate().declareExchange(topicExchange); + return topicExchange; + } + + public FanoutExchange declare(FanoutExchange fanoutExchange) { + rabbitAdminTemplate().declareExchange(fanoutExchange); + return fanoutExchange; + } + +} diff --git a/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/test/RabbitConfiguration.java b/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/test/RabbitConfiguration.java new file mode 100644 index 00000000..fa5261bf --- /dev/null +++ b/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/test/RabbitConfiguration.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.test; + +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; + +/** + * RabbitConfiguration defines class-level metadata for configuring Rabbit integration tests. + * + * @author Mark Pollack + * + */ +@Documented +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface RabbitConfiguration { + + String hostname() default "localhost"; + + String username() default "guest"; + + String password() default "guest"; +} diff --git a/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/test/RabbitTestExecutionListener.java b/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/test/RabbitTestExecutionListener.java new file mode 100644 index 00000000..a0164c62 --- /dev/null +++ b/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/test/RabbitTestExecutionListener.java @@ -0,0 +1,142 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.test; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.amqp.rabbit.admin.RabbitAdminTemplate; +import org.springframework.amqp.rabbit.admin.RabbitStatus; +import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.otp.erlang.OtpIOException; +import org.springframework.test.context.TestContext; +import org.springframework.test.context.support.AbstractTestExecutionListener; +import org.springframework.test.context.transaction.TransactionConfiguration; +import org.springframework.test.context.transaction.TransactionConfigurationAttributes; + +/** + * A TestExeuctionListener that will start/reset the RabbitMQ node before the methods in the + * TestClass are executed. If the node was started before the test methods were executed, + * the node will be stopped after the test methods are executed . + * + * @author Mark Pollack + * + */ +public class RabbitTestExecutionListener extends AbstractTestExecutionListener{ + + private static final Log logger = LogFactory.getLog(RabbitTestExecutionListener.class); + private RabbitAdminTemplate rabbitAdminTemplate; + private boolean startedNode; + + + @Override + public void beforeTestClass(TestContext testContext) throws Exception { + initializeRabbitAdminTemplate(testContext); + recycleBrokerApp(); + } + + @Override + public void afterTestClass(TestContext testContext) throws Exception { + + if (startedNode) { + rabbitAdminTemplate.stopNode(); + startedNode = false; + } + } + + private void recycleBrokerApp() { + try { + RabbitStatus status = rabbitAdminTemplate.getStatus(); + logger.debug(status); + rabbitAdminTemplate.stopBrokerApplication(); + rabbitAdminTemplate.resetNode(); + rabbitAdminTemplate.startBrokerApplication(); + + } catch (OtpIOException e) { + // Can't connect because broker node isn't running. + rabbitAdminTemplate.startNode(); + startedNode = true; + //TODO - need to wait on output for 'broker running' + try { + Thread.sleep(2000); + } catch (InterruptedException e1) { + // TODO Auto-generated catch block + logger.error("Error waiting for broker to start"); + } + } + + + + } + + /** + * Retrieves the {@link TransactionConfigurationAttributes} for the + * specified {@link Class class} which may optionally declare or inherit a + * {@link TransactionConfiguration @TransactionConfiguration}. If a + * {@link TransactionConfiguration} annotation is not present for the + * supplied class, the default values for attributes defined in + * {@link TransactionConfiguration} will be used instead. + * @param clazz the Class object corresponding to the test class for which + * the configuration attributes should be retrieved + * @return a new TransactionConfigurationAttributes instance + */ + private void initializeRabbitAdminTemplate(TestContext testContext) { + + //Could potentially get the information from the BeanFactory by implementing a BeanFactoryPostProcessor + if (this.rabbitAdminTemplate == null) { + Class clazz = testContext.getTestClass(); + Class annotationType = RabbitConfiguration.class; + RabbitConfiguration config = clazz.getAnnotation(annotationType); + if (logger.isDebugEnabled()) { + logger.debug("Retrieved @RabbitConfiguration [" + config + "] for test class [" + clazz + "]"); + } + + String hostname; + String username; + String password; + if (config != null) { + hostname = config.hostname(); + username = config.username(); + password = config.password(); + } + else { + hostname = (String) AnnotationUtils.getDefaultValue(annotationType, "hostname"); + username = (String) AnnotationUtils.getDefaultValue(annotationType, "username"); + password = (String) AnnotationUtils.getDefaultValue(annotationType, "password"); + } + if (logger.isDebugEnabled()) { + logger.debug( + String.format("Retrieved hostname=[%s] username=[%s], password=[%s] for class [%s]", + hostname, username, password, clazz)); + } + + CachingConnectionFactory ccf; + if (hostname.equals("localhost")) { + //This will try to get the local host name + ccf = new CachingConnectionFactory(); + } else { + ccf = new CachingConnectionFactory(hostname); + } + ccf.setUsername(username); + ccf.setPassword(password); + ccf.afterPropertiesSet(); + rabbitAdminTemplate = new RabbitAdminTemplate(ccf); + } + } + +} diff --git a/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/test/SpringRabbitJUnit4ClassRunner.java b/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/test/SpringRabbitJUnit4ClassRunner.java new file mode 100644 index 00000000..6b393a77 --- /dev/null +++ b/spring-rabbit-admin/src/main/java/org/springframework/amqp/rabbit/test/SpringRabbitJUnit4ClassRunner.java @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.test; + +import java.lang.reflect.Field; +import java.util.ArrayList; + +import org.junit.runners.model.InitializationError; +import org.springframework.test.context.TestContextManager; +import org.springframework.test.context.TestExecutionListener; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.ReflectionUtils; + +/** + * A specialized JUnit4 class runner so that we can add a RabbitTestExecutionListener. + * + * The RabbitTestExecutionListener needs to run before the DependencyInjectionTestExecutionListener + * so that the RabbitMQ broker application and be 'reset' or the RabbitMQ node + * started before the application context is created so that RabbitMQ broker configuration can + * execute when the applicatin context is created. + * + * + * @author Mark Pollack + * + */ +public class SpringRabbitJUnit4ClassRunner extends SpringJUnit4ClassRunner { + + public SpringRabbitJUnit4ClassRunner(Class clazz) + throws InitializationError { + super(clazz); + } + + + /** + * Creates a new {@link TestContextManager} for the supplied test class and + * the configured default ContextLoader class name. + * Can be overridden by subclasses. + * + * @param clazz the test class to be managed + * @see #getDefaultContextLoaderClassName(Class) + */ + protected TestContextManager createTestContextManager(Class clazz) { + TestContextManager mgr = new TestContextManager(clazz, getDefaultContextLoaderClassName(clazz)); + + Field executionListenersField = ReflectionUtils.findField(TestContextManager.class, "testExecutionListeners"); + executionListenersField.setAccessible(true); + ArrayList executionListeners = + (ArrayList) ReflectionUtils.getField(executionListenersField, mgr); + ArrayList newExecutionListeners = new ArrayList(); + newExecutionListeners.add(new RabbitTestExecutionListener()); + for (TestExecutionListener testExecutionListener : executionListeners) { + newExecutionListeners.add(testExecutionListener); + } + ReflectionUtils.setField(executionListenersField, mgr, newExecutionListeners); + + //puts to the back of the list - need it before context is created as broker configuration instructions are + //executed as part of the context creation. + //mgr.registerTestExecutionListeners(new RabbitTestExecutionListener()); + + return mgr; + } + + +} diff --git a/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/admin/JInterfaceIntegrationTests.java b/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/admin/JInterfaceIntegrationTests.java new file mode 100644 index 00000000..d55d69dc --- /dev/null +++ b/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/admin/JInterfaceIntegrationTests.java @@ -0,0 +1,91 @@ +package org.springframework.amqp.rabbit.admin; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; + +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.otp.erlang.connection.ConnectionParameters; +import org.springframework.otp.erlang.connection.SimpleConnectionFactory; +import org.springframework.otp.erlang.core.ErlangTemplate; + +import com.ericsson.otp.erlang.OtpAuthException; +import com.ericsson.otp.erlang.OtpConnection; +import com.ericsson.otp.erlang.OtpErlangBinary; +import com.ericsson.otp.erlang.OtpErlangExit; +import com.ericsson.otp.erlang.OtpErlangList; +import com.ericsson.otp.erlang.OtpErlangObject; +import com.ericsson.otp.erlang.OtpPeer; +import com.ericsson.otp.erlang.OtpSelf; + +//@Ignore("manual integration test only.") +public class JInterfaceIntegrationTests { + + @Test + public void rawApi() { + String markpCookie = "HRVDLVJTUELWREUSYOCA"; + OtpConnection connection = null; + try { + OtpSelf self = new OtpSelf("rabbit-monitor"); + + String hostName = "rabbit@" + + InetAddress.getLocalHost().getHostName(); + OtpPeer peer = new OtpPeer(hostName); + connection = self.connect(peer); + // connection.sendRPC("erlang","date", new OtpErlangList()); + // connection.sendRPC("rabbit_access_control", "list_vhosts", new + // OtpErlangList()); + OtpErlangObject[] objectArray = { new OtpErlangBinary("/" + .getBytes()) }; + + connection.sendRPC("rabbit_amqqueue", "info_all", + new OtpErlangList(objectArray)); + + // connection.sendRPC("rabbit_amqqueue", "stat_all", new + // OtpErlangList()); + + OtpErlangObject received = connection.receiveRPC(); + System.out.println(received); + System.out.println(received.getClass()); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (OtpAuthException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (OtpErlangExit e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } finally { + if (connection != null) { + connection.close(); + } + } + + } + + @Test + public void otpTemplate() throws UnknownHostException { + String selfNodeName = "rabbit-monitor"; + String peerNodeName = "rabbit@" + + InetAddress.getLocalHost().getHostName(); + + String home = System.getProperty("user.home"); + System.out.println("home = " + home); + System.out.println("peerNodeName = " + peerNodeName); + + SimpleConnectionFactory cf = new SimpleConnectionFactory(selfNodeName, + peerNodeName); + + cf.afterPropertiesSet(); + ErlangTemplate template = new ErlangTemplate(cf); + template.afterPropertiesSet(); + + OtpErlangObject result = template.executeRpc("rabbit_amqqueue", + "info_all", "/".getBytes()); + System.out.println(result); + System.out.println(result.getClass()); + + } +} diff --git a/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/admin/RabbitAdminTemplateTests.java b/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/admin/RabbitAdminTemplateTests.java new file mode 100644 index 00000000..2271d249 --- /dev/null +++ b/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/admin/RabbitAdminTemplateTests.java @@ -0,0 +1,115 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.admin; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; + +import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.otp.erlang.OtpIOException; + +/** + * @author Mark Pollack + */ +public class RabbitAdminTemplateTests { + + private static RabbitAdminTemplate adminTemplate; + + @BeforeClass + public static void setUp() { + CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); + connectionFactory.setUsername("guest"); + connectionFactory.setPassword("guest"); + connectionFactory.setChannelCacheSize(10); + RabbitTemplate template = new RabbitTemplate(connectionFactory); + adminTemplate = new RabbitAdminTemplate(template); + } + + @Test + @Ignore + public void integrationTestsUserCrud() { + List users = adminTemplate.listUsers(); + if (users.contains("joe")) { + adminTemplate.deleteUser("joe"); + } + adminTemplate.addUser("joe", "trader"); + adminTemplate.changeUserPassword("joe", "sales"); + users = adminTemplate.listUsers(); + if (users.contains("joe")) { + adminTemplate.deleteUser("joe"); + } + } + + public void integrationTestListUsers() { + // OtpErlangObject result = + // adminTemplate.getErlangTemplate().executeRpc("rabbit_amqqueue", + // "info_all", "/".getBytes()); + // System.out.println(result); + List users = adminTemplate.listUsers(); + System.out.println(users); + } + + public void integrationTestDeleteUser() { + // OtpErlangObject result = + // adminTemplate.getErlangTemplate().executeRpc("rabbit_access_control", + // "delete_user", "joe".getBytes()); + adminTemplate.deleteUser("joe"); + // System.out.println(result.getClass()); + // System.out.println(result); + } + + @Test + @Ignore + public void testStatusAndBrokerLifecycle() { + RabbitStatus status = adminTemplate.getStatus(); + assertBrokerAppRunning(status); + + adminTemplate.stopBrokerApplication(); + status = adminTemplate.getStatus(); + assertEquals(0, status.getRunningNodes().size()); + + adminTemplate.startBrokerApplication(); + status = adminTemplate.getStatus(); + assertBrokerAppRunning(status); + } + + @Test + @Ignore("NEEDS RABBITMQ_HOME to be set.") + public void testStartNode() { + try { + adminTemplate.stopNode(); + } catch (OtpIOException e) { + //assume it is not running. + } + adminTemplate.startNode(); + assertEquals(1,1); + adminTemplate.stopNode(); + } + + + private void assertBrokerAppRunning(RabbitStatus status) { + assertEquals(1, status.getRunningNodes().size()); + assertTrue(status.getRunningNodes().get(0).getName().contains("rabbit")); + } +} diff --git a/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/admin/RabbitConsumerConfiguration.java b/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/admin/RabbitConsumerConfiguration.java new file mode 100644 index 00000000..affae695 --- /dev/null +++ b/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/admin/RabbitConsumerConfiguration.java @@ -0,0 +1,26 @@ +package org.springframework.amqp.rabbit.admin; + +import org.springframework.amqp.rabbit.core.Constants; +import org.springframework.amqp.rabbit.core.PojoHandler; +import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; +import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RabbitConsumerConfiguration extends TestRabbitConfiguration { + + @Bean + public SimpleMessageListenerContainer simpleMessageListenerContainer() { + SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); + container.setConnectionFactory(connectionFactory()); + container.setQueueName(Constants.QUEUE_NAME); + container.setConcurrentConsumers(5); + + MessageListenerAdapter adapter = new MessageListenerAdapter(); + adapter.setDelegate(new PojoHandler()); + container.setMessageListener(adapter); + + return container; + } +} diff --git a/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/admin/RabbitProducerConfiguration.java b/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/admin/RabbitProducerConfiguration.java new file mode 100644 index 00000000..abdc3e05 --- /dev/null +++ b/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/admin/RabbitProducerConfiguration.java @@ -0,0 +1,36 @@ +package org.springframework.amqp.rabbit.admin; + +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.DirectExchange; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.rabbit.core.Constants; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RabbitProducerConfiguration extends TestRabbitConfiguration { + + + public RabbitProducerConfiguration() { + super(); + } + + @Bean + public Queue fooQueue() { + return declare(new Queue(Constants.QUEUE_NAME)); + + } + + @Bean + public DirectExchange directExchange() + { + // This is the same as the default exchanage. TODO consider predefined + return declare(DirectExchange.DEFAULT); + } + + @Bean + public Binding fooBinding() + { + return declare(new Binding(fooQueue(), directExchange(), Constants.ROUTING_KEY)); + } +} diff --git a/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/admin/RabbitTemplateConsumerExample.java b/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/admin/RabbitTemplateConsumerExample.java new file mode 100644 index 00000000..b168b14f --- /dev/null +++ b/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/admin/RabbitTemplateConsumerExample.java @@ -0,0 +1,111 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.admin; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageListener; +import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; +import org.springframework.amqp.rabbit.core.Constants; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; +import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; + +/** + * @author Mark Fisher + * @author Mark Pollack + */ +@SuppressWarnings("unused") +public class RabbitTemplateConsumerExample { + + private static Log log = LogFactory + .getLog(RabbitTemplateConsumerExample.class); + + public static void main(String[] args) throws Exception { + + boolean sync = true; + if (sync) { + ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext( + TestRabbitConfiguration.class); + RabbitTemplate template = ctx.getBean(RabbitTemplate.class); + receiveSync(template, Constants.NUM_MESSAGES); + } else { + ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext( + RabbitConsumerConfiguration.class); + receiveAsync(ctx); + } + + } + + private static void receiveAsync(ConfigurableApplicationContext ctx) + throws InterruptedException { + + SimpleMessageListenerContainer container = ctx + .getBean(SimpleMessageListenerContainer.class); + + log.debug("Main execution thread sleeping 5 seconds..."); + Thread.sleep(500000); + log.debug("Application exiting."); + + System.exit(0); + + } + + private static void receiveSync(RabbitTemplate template, int numMessages) { + // receive response + for (int i = 0; i < numMessages; i++) { + Message message = template.receive(Constants.QUEUE_NAME); + if (message == null) { + System.out.println("Thread [" + Thread.currentThread().getId() + + "] Received Null Message!"); + } else { + System.out.println("Thread [" + Thread.currentThread().getId() + + "] Received Message = " + + new String(message.getBody())); + Map headers = message.getMessageProperties() + .getHeaders(); + + Object objFloat = headers.get("float"); + Object objcp = headers.get("object"); + System.out.println("float header type = " + objFloat.getClass()); + System.out.println("object header type = " + objcp.getClass()); + } + } + } + + public static class SimpleMessageListener implements MessageListener { + + private final AtomicInteger messageCount = new AtomicInteger(); + + public void onMessage(Message message) { + int msgCount = this.messageCount.incrementAndGet(); + System.out.println("Thread [" + Thread.currentThread().getId() + + "] SimpleMessageListener Received Message " + msgCount + + ", = " + new String(message.getBody())); + } + } +} diff --git a/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/admin/RabbitTemplateProducerExample.java b/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/admin/RabbitTemplateProducerExample.java new file mode 100644 index 00000000..5dd7752e --- /dev/null +++ b/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/admin/RabbitTemplateProducerExample.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.admin; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +/** + * @author Mark Fisher + */ +public class RabbitTemplateProducerExample { + + private static Log log = LogFactory.getLog(RabbitTemplateProducerExample.class); + + public static void main(String[] args) throws Exception { + + ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(RabbitProducerConfiguration.class); + RabbitTemplate template = ctx.getBean(RabbitTemplate.class); + + for (int i = 1; i <= 10; i++) { + template.convertAndSend("test-" + i); + Thread.sleep(100); + } + log.debug("done sending"); + ctx.stop(); + System.exit(0); + } + +} diff --git a/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/admin/TestRabbitConfiguration.java b/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/admin/TestRabbitConfiguration.java new file mode 100644 index 00000000..874c7b34 --- /dev/null +++ b/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/admin/TestRabbitConfiguration.java @@ -0,0 +1,31 @@ +package org.springframework.amqp.rabbit.admin; + +import org.springframework.amqp.rabbit.admin.config.AbstractRabbitConfiguration; +import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.Constants; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.context.annotation.Bean; + +public class TestRabbitConfiguration extends AbstractRabbitConfiguration { + + @Bean + public RabbitTemplate rabbitTemplate() { + RabbitTemplate template = new RabbitTemplate(connectionFactory()); + template.setDefaultExchange(Constants.EXCHANGE_NAME); + template.setDefaultRoutingKey(Constants.ROUTING_KEY); + return template; + } + + @Bean + public ConnectionFactory connectionFactory() { + + //TODO make it possible to customize in subclasses. + CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost"); + connectionFactory.setUsername("guest"); + connectionFactory.setPassword("guest"); + connectionFactory.setChannelCacheSize(10); + return connectionFactory; + } + +} diff --git a/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/test/RabbitTestExecutionListenerIntegrationTests.java b/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/test/RabbitTestExecutionListenerIntegrationTests.java new file mode 100644 index 00000000..6af8af72 --- /dev/null +++ b/spring-rabbit-admin/src/test/java/org/springframework/amqp/rabbit/test/RabbitTestExecutionListenerIntegrationTests.java @@ -0,0 +1,27 @@ +package org.springframework.amqp.rabbit.test; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.ContextConfiguration; + +@RunWith(SpringRabbitJUnit4ClassRunner.class) +@ContextConfiguration +@RabbitConfiguration +public class RabbitTestExecutionListenerIntegrationTests { + + private static int count; + + @Test + public void doNothing() throws InterruptedException { + Thread.sleep(1000); + System.out.println("inside DO NOTHING"); + System.out.println("inside DO NOTHING"); + } + + @Test + public void doNothinAgain() throws InterruptedException { + Thread.sleep(1000); + System.out.println("inside DO AGAIN"); + System.out.println("inside DO AGAIN"); + } +} diff --git a/spring-rabbit-admin/src/test/resources/org/springframework/amqp/rabbit/test/ConfigureBrokerAnntotationTests-context.xml b/spring-rabbit-admin/src/test/resources/org/springframework/amqp/rabbit/test/ConfigureBrokerAnntotationTests-context.xml new file mode 100644 index 00000000..16bd4061 --- /dev/null +++ b/spring-rabbit-admin/src/test/resources/org/springframework/amqp/rabbit/test/ConfigureBrokerAnntotationTests-context.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/spring-rabbit-admin/template.mf b/spring-rabbit-admin/template.mf new file mode 100644 index 00000000..c765379e --- /dev/null +++ b/spring-rabbit-admin/template.mf @@ -0,0 +1,13 @@ +Bundle-SymbolicName: org.springframework.amqp.rabbit.admin +Bundle-Name: Spring RabbitMQ Administrative Support +Bundle-Vendor: SpringSource +Bundle-ManifestVersion: 2 +Import-Template: + org.springframework.*;version="[3.0.3, 4.0.0)", + org.springframework.amqp.*;version="[1.0.0, 1.1.0)", + org.apache.commons.logging;version="[1.1.1, 2.0.0)", + com.rabbitmq.*;version="[1.7.2, 2.0.0)", + com.ericsson.otp.erlang;version="[1.5.3, 2.0.0)", + org.junit.*;version="0", + org.w3c.dom.*;version="0", + javax.xml.*;version="0" diff --git a/spring-rabbit/.classpath b/spring-rabbit/.classpath new file mode 100644 index 00000000..7a0eedb4 --- /dev/null +++ b/spring-rabbit/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/spring-rabbit/.project b/spring-rabbit/.project new file mode 100644 index 00000000..aa358fe8 --- /dev/null +++ b/spring-rabbit/.project @@ -0,0 +1,35 @@ + + + spring-rabbit + + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.maven.ide.eclipse.maven2Builder + + + + + org.springframework.ide.eclipse.core.springbuilder + + + + + + org.springframework.ide.eclipse.core.springnature + org.maven.ide.eclipse.maven2Nature + org.eclipse.jdt.core.javanature + org.eclipse.wst.common.project.facet.core.nature + + diff --git a/spring-rabbit/.settings/org.maven.ide.eclipse.prefs b/spring-rabbit/.settings/org.maven.ide.eclipse.prefs new file mode 100644 index 00000000..3b616447 --- /dev/null +++ b/spring-rabbit/.settings/org.maven.ide.eclipse.prefs @@ -0,0 +1,9 @@ +#Tue Jun 29 21:59:01 EDT 2010 +activeProfiles= +eclipse.preferences.version=1 +fullBuildGoals=process-test-resources +includeModules=false +resolveWorkspaceProjects=true +resourceFilterGoals=process-resources resources\:testResources +skipCompilerPlugin=true +version=1 diff --git a/spring-rabbit/.springBeans b/spring-rabbit/.springBeans new file mode 100644 index 00000000..dbe284d5 --- /dev/null +++ b/spring-rabbit/.springBeans @@ -0,0 +1,13 @@ + + + 1 + + + + + + + + + + diff --git a/spring-rabbit/pom.xml b/spring-rabbit/pom.xml new file mode 100644 index 00000000..79037b6e --- /dev/null +++ b/spring-rabbit/pom.xml @@ -0,0 +1,119 @@ + + 4.0.0 + + org.springframework.amqp + spring-amqp-parent + 1.0.0.BUILD-SNAPSHOT + ../spring-amqp-parent/pom.xml + + spring-rabbit + jar + Spring RabbitMQ Support + + + com.rabbitmq + amqp-client + + + + org.springframework.amqp + spring-amqp-core + + + + + org.springframework + spring-aop + + + org.springframework + spring-oxm + + + org.springframework + spring-tx + + + org.springframework + spring-test + + + + + commons-io + commons-io + + + + + org.slf4j + slf4j-api + + + org.slf4j + jcl-over-slf4j + runtime + + + org.slf4j + slf4j-log4j12 + runtime + + + log4j + log4j + + + javax.mail + mail + + + javax.jms + jms + + + com.sun.jdmk + jmxtools + + + com.sun.jmx + jmxri + + + runtime + + + + javax.annotation + jsr250-api + true + + + + cglib + cglib-nodep + true + + + + org.mockito + mockito-all + test + + + + junit + junit + + + + + + + com.springsource.bundlor + com.springsource.bundlor.maven + + + + diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/NamespaceUtils.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/NamespaceUtils.java new file mode 100644 index 00000000..af0bdc12 --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/NamespaceUtils.java @@ -0,0 +1,171 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.config; + +import java.util.List; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.core.Conventions; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; + +/** + * Shared utility methods for namespace parsers. + * + */ +public abstract class NamespaceUtils { + + static final String BASE_PACKAGE = "org.springframework.amqp.core.rabbit.config"; + static final String REF_ATTRIBUTE = "ref"; + static final String METHOD_ATTRIBUTE = "method"; + static final String ORDER = "order"; + + + + /** + * Populates the specified bean definition property with the value + * of the attribute whose name is provided if that attribute is + * defined in the given element. + * + * @param beanDefinition the bean definition to be configured + * @param element the XML element where the attribute should be defined + * @param attributeName the name of the attribute whose value will be + * used to populate the property + * @param propertyName the name of the property to be populated + */ + public static void setValueIfAttributeDefined(BeanDefinitionBuilder builder, + Element element, String attributeName, String propertyName) { + String attributeValue = element.getAttribute(attributeName); + if (StringUtils.hasText(attributeValue)) { + builder.addPropertyValue(propertyName, attributeValue); + } + } + + /** + * Populates the bean definition property corresponding to the specified + * attributeName with the value of that attribute if it is defined in the + * given element. + * + *

The property name will be the camel-case equivalent of the lower + * case hyphen separated attribute (e.g. the "foo-bar" attribute would + * match the "fooBar" property). + * + * @see Conventions#attributeNameToPropertyName(String) + * + * @param beanDefinition - the bean definition to be configured + * @param element - the XML element where the attribute should be defined + * @param attributeName - the name of the attribute whose value will be set + * on the property + */ + public static void setValueIfAttributeDefined(BeanDefinitionBuilder builder, + Element element, String attributeName) { + setValueIfAttributeDefined(builder, element, attributeName, + Conventions.attributeNameToPropertyName(attributeName)); + } + + /** + * Populates the specified bean definition property with the reference + * to a bean. The bean reference is identified by the value from the + * attribute whose name is provided if that attribute is defined in + * the given element. + * + * @param beanDefinition the bean definition to be configured + * @param element the XML element where the attribute should be defined + * @param attributeName the name of the attribute whose value will be + * used as a bean reference to populate the property + * @param propertyName the name of the property to be populated + */ + public static void setReferenceIfAttributeDefined(BeanDefinitionBuilder builder, + Element element, String attributeName, String propertyName) { + String attributeValue = element.getAttribute(attributeName); + if (StringUtils.hasText(attributeValue)) { + builder.addPropertyReference(propertyName, attributeValue); + } + } + + /** + * Populates the bean definition property corresponding to the specified + * attributeName with the reference to a bean identified by the value of + * that attribute if the attribute is defined in the given element. + * + *

The property name will be the camel-case equivalent of the lower + * case hyphen separated attribute (e.g. the "foo-bar" attribute would + * match the "fooBar" property). + * + * @see Conventions#attributeNameToPropertyName(String) + * + * @param beanDefinition - the bean definition to be configured + * @param element - the XML element where the attribute should be defined + * @param attributeName - the name of the attribute whose value will be + * used as a bean reference to populate the property + * + * @see Conventions#attributeNameToPropertyName(String) + */ + public static void setReferenceIfAttributeDefined(BeanDefinitionBuilder builder, + Element element, String attributeName) { + setReferenceIfAttributeDefined(builder, element, attributeName, + Conventions.attributeNameToPropertyName(attributeName)); + } + + /** + * Provides a user friendly description of an element based on its node + * name and, if available, its "id" attribute value. This is useful for + * creating error messages from within bean definition parsers. + */ + public static String createElementDescription(Element element) { + String elementId = "'" + element.getNodeName() + "'"; + String id = element.getAttribute("id"); + if (StringUtils.hasText(id)) { + elementId += " with id='" + id + "'"; + } + return elementId; + } + + public static BeanComponentDefinition parseInnerHandlerDefinition(Element element, ParserContext parserContext){ + // parses out inner bean definition for concrete implementation if defined + List childElements = DomUtils.getChildElementsByTagName(element, "bean"); + BeanComponentDefinition innerComponentDefinition = null; + if (childElements != null && childElements.size() == 1){ + Element beanElement = childElements.get(0); + BeanDefinitionParserDelegate delegate = parserContext.getDelegate(); + BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(beanElement); + bdHolder = delegate.decorateBeanDefinitionIfRequired(beanElement, bdHolder); + BeanDefinition inDef = bdHolder.getBeanDefinition(); + String beanName = BeanDefinitionReaderUtils.generateBeanName(inDef, parserContext.getRegistry()); + innerComponentDefinition = new BeanComponentDefinition(inDef, beanName); + parserContext.registerBeanComponent(innerComponentDefinition); + } + + String ref = element.getAttribute(REF_ATTRIBUTE); + Assert.isTrue(!(StringUtils.hasText(ref) && innerComponentDefinition != null), "Ambiguous definition. Inner bean " + + (innerComponentDefinition == null + ? innerComponentDefinition + : innerComponentDefinition.getBeanDefinition().getBeanClassName()) + " declaration and \"ref\" " + ref + + " are not allowed together."); + return innerComponentDefinition; + } + +} diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/RabbitAdminParser.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/RabbitAdminParser.java new file mode 100644 index 00000000..60c5f651 --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/RabbitAdminParser.java @@ -0,0 +1,86 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.config; + +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * Start of implementation for a potential XML based broker administration schema. + * @author Mark Pollack + * + */ +public class RabbitAdminParser extends AbstractBeanDefinitionParser { + + + protected static final String EXCHANGES_ELEMENT = "exchanges"; + + protected static final String DIRECT_EXCHANGE_ELEMENT = "direct-exchange"; + + @Override + protected boolean shouldGenerateId() { + return false; + } + + @Override + protected boolean shouldGenerateIdAsFallback() { + return true; + } + + @Override + protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { + + 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 = parserContext.getDelegate().getLocalName(child); + if (EXCHANGES_ELEMENT.equals(localName)) { + parseExchanges((Element) child, element, parserContext); + } + } + } + + return null; + } + + private void parseExchanges(Element exchangesElement, Element element, ParserContext parserContext) { + + NodeList exchangeNodes = exchangesElement.getChildNodes(); + for (int i = 0; i < exchangeNodes.getLength(); i++) { + Node exchange = exchangeNodes.item(i); + if (exchange.getNodeType() == Node.ELEMENT_NODE) { + String localName = parserContext.getDelegate().getLocalName(exchange); + if (DIRECT_EXCHANGE_ELEMENT.equals(localName)) { + parseDirectExchange((Element) exchange, exchangesElement, parserContext); + } + } + } + + } + + private void parseDirectExchange(Element directExchangeElement, Element exchangesElement, + ParserContext parserContext) { + + + } + +} diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/RabbitNamespaceHandler.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/RabbitNamespaceHandler.java new file mode 100644 index 00000000..3357e37c --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/RabbitNamespaceHandler.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.config; + +import org.springframework.beans.factory.xml.NamespaceHandlerSupport; + +/** + * Start of namespace handler for Rabbit + * @author Mark Pollack + * + */ +public class RabbitNamespaceHandler extends NamespaceHandlerSupport { + + public void init() { + registerBeanDefinitionParser("rabbit-admin", new RabbitAdminParser()); + } + +} diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/CachingConnectionFactory.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/CachingConnectionFactory.java new file mode 100644 index 00000000..fe692a80 --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/CachingConnectionFactory.java @@ -0,0 +1,465 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.connection; + +import java.io.IOException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.amqp.rabbit.support.RabbitUtils; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionParameters; + +/** + * A {@link ConnectionFactory} implementation that returns the same Connections from all + * {@link #createConnection()} calls, and ignores calls to {@link com.rabbitmq.client.Connection#close()} and + * caches {@link com.rabbitmq.client.Channel}. + * + *

By default, only one single Session will be cached, with further requested Channels being created and + * disposed on demand. Consider raising the {@link #setChannelCacheSize(int) "channelCacheSize" value} in case of a + * high-concurrency environment. + * + *

NOTE: This ConnectionFactory requires explicit closing of all Channels obtained form its + * shared Connection. This is the usual recommendation for native Rabbit access code anyway. + * However, with this ConnectionFactory, its use is mandator in order to actually allow for Channel reuse. + * + * @author Mark Pollack + */ +//TODO are there heartbeats and/or exception thrown if a connection is broken? +public class CachingConnectionFactory implements ConnectionFactory, InitializingBean, DisposableBean { + + protected final Log logger = LogFactory.getLog(getClass()); + + private final String hostName; + + private volatile int portNumber = RabbitUtils.DEFAULT_PORT; + + private int channelCacheSize = 1; + + private com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory; + + /** Raw Rabbit Connection */ + private Connection targetConnection; + + /** Proxy Connection */ + private Connection connection; + + /** Synchronization monitor for the shared Connection */ + private final Object connectionMonitor = new Object(); + + private final LinkedList cachedChannels = new LinkedList(); + + private volatile boolean active = true; + + /** + * Create a new CachingConnectionFactory initializing the hostname to be the + * value returned from InetAddress.getLocalHost(), or "localhost" if getLocalHost() throws + * an exception. + */ + public CachingConnectionFactory() { + this.hostName = initializeDefaultHostName(); + this.rabbitConnectionFactory = new com.rabbitmq.client.ConnectionFactory(); + } + + /** + * Create a new CachingConnectionFactory given a host name. + * @param hostName the host name to connect to + */ + public CachingConnectionFactory(String hostName) { + this(new com.rabbitmq.client.ConnectionFactory(), hostName); + } + + /** + * Create a new CachingConnectionFactory given ConnectionParameters and the host name. + * @param connectionParameters the connection parameters to use when creating a connection + * @param hostName the host name to connect to + */ + public CachingConnectionFactory(ConnectionParameters connectionParameters, String hostName) { + this(new com.rabbitmq.client.ConnectionFactory(connectionParameters), hostName); + } + + /** + * Create a new CachingConnectionFactory for the given target + * ConnectionFactory. + * @param rabbitConnectionFactory the target ConnectionFactory + * @param hostName the host name to connect to + */ + public CachingConnectionFactory(com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory, String hostName) { + Assert.notNull(rabbitConnectionFactory, "Target ConnectionFactory must not be null"); + Assert.hasText(hostName, "hostName must not be empty"); + this.rabbitConnectionFactory = rabbitConnectionFactory; + this.hostName = hostName; + } + + public com.rabbitmq.client.ConnectionFactory getRabbitConnectionFactory() { + return this.rabbitConnectionFactory; + } + + public void setUsername(String username) { + this.rabbitConnectionFactory.getParameters().setUsername(username); + } + + public void setPassword(String password) { + this.rabbitConnectionFactory.getParameters().setPassword(password); + } + + public void setChannelCacheSize(int sessionCacheSize) { + Assert.isTrue(sessionCacheSize >= 1, "Channel cache size must be 1 or higher"); + this.channelCacheSize = sessionCacheSize; + } + + + + public int getChannelCacheSize() { + return this.channelCacheSize; + } + + + public String getHostName() { + return hostName; + } + + protected Channel getChannel(Connection connection) throws Exception { + LinkedList channelList = this.cachedChannels; + Channel channel = null; + synchronized (channelList) { + if (!channelList.isEmpty()) { + channel = channelList.removeFirst(); + } + } + if (channel != null) { + if (logger.isTraceEnabled()) { + logger.trace("Found cached Rabbit Channel"); + } + } + else { + Channel targetChannel = createChannel(connection); + if (logger.isDebugEnabled()) { + logger.debug("Creating cached Rabbit Channel"); + } + channel = getCachedChannelProxy(targetChannel, channelList); + } + return channel; + } + + private Channel createChannel(Connection connection) throws IOException { + //TODO overload with channel number. + return connection.createChannel(); + } + + protected Channel getCachedChannelProxy(Channel target, LinkedList channelList) { + return (Channel) Proxy.newProxyInstance( + ChannelProxy.class.getClassLoader(), + new Class[] {ChannelProxy.class}, + new CachedChannelInvocationHandler(target, channelList)); + } + + /** + * Make sure a Connection or ConnectionFactory has been set. + */ + public void afterPropertiesSet() { + if (getRabbitConnectionFactory() == null) { + throw new IllegalArgumentException("Connection or 'RabbitConnectionFactory' is required"); + } + } + + public Connection createConnection() throws IOException { + + synchronized (this.connectionMonitor) { + if (this.connection == null) { + initConnection(); + } + return this.connection; + } + } + + public ConnectionParameters getParameters() { + return this.rabbitConnectionFactory.getParameters(); + } + + + + + + public void initConnection() throws IOException { + if (getRabbitConnectionFactory() == null) { + throw new IllegalStateException( + "'rabbitConnectionFactory' is required for lazily initializing a Connection"); + } + synchronized (this.connectionMonitor) { + if (this.targetConnection != null) { + closeConnection(this.targetConnection); + } + this.targetConnection = doCreateConnection(); + prepareConnection(this.targetConnection); + if (logger.isInfoEnabled()) { + logger.info("Established shared Rabbit Connection: " + this.targetConnection); + } + this.connection = getSharedConnectionProxy(this.targetConnection); + } + } + + + + /** + * Close the underlying shared connection. + * The provider of this ConnectionFactory needs to care for proper shutdown. + *

As this bean implements DisposableBean, a bean factory will + * automatically invoke this on destruction of its cached singletons. + */ + public void destroy() { + resetConnection(); + } + + + /** + * Reset the Channel cache and underlying shared Connection, to be reinitialized on next access. + */ + public void resetConnection() { + this.active = false; + synchronized (this.cachedChannels) { + for (Channel channel : cachedChannels) { + try { + channel.close(); + } catch (Throwable ex) { + logger.trace("Could not close cached Rabbit Channel", ex); + } + } + this.cachedChannels.clear(); + } + this.active = true; + + synchronized (this.connectionMonitor) { + if (this.targetConnection != null) { + closeConnection(this.targetConnection); + } + this.targetConnection = null; + this.connection = null; + } + } + /** + * Close the given Connection. + * @param con the Connection to close + */ + protected void closeConnection(Connection con) { + if (logger.isDebugEnabled()) { + logger.debug("Closing shared Rabbit Connection: " + this.targetConnection); + } + try { + //TODO there are other close overloads close(int closeCode, java.lang.String closeMessage, int timeout) + con.close(); + } + catch (Throwable ex) { + logger.debug("Could not close shared Rabbit Connection", ex); + } + } + + /** + * Create a Rabbit Connection via this class's ConnectionFactory. + * @return the new Rabbit Connection + */ + protected Connection doCreateConnection() throws IOException { + //TODO there are other overloaded .newConnection methods + return getRabbitConnectionFactory().newConnection(this.hostName, this.portNumber); + } + + protected void prepareConnection(Connection con) throws IOException { + //TODO configure ShutdownListener, investigate reconnection exceptions + } + + protected String initializeDefaultHostName() { + String temp; + try { + InetAddress localMachine = InetAddress.getLocalHost(); + temp = localMachine.getHostName(); + logger.debug("Using hostname [" + temp + "] for hostname."); + } catch (UnknownHostException e) { + logger.warn("Could not get host name, using 'localhost' as default value", e); + temp = "localhost"; + } + return temp; + } + + /** + * Wrap the given Connection with a proxy that delegates every method call to it + * but suppresses close calls. This is useful for allowing application code to + * handle a special framework Connection just like an ordinary Connection from a + * Rabbit ConnectionFactory. + * @param target the original Connection to wrap + * @return the wrapped Connection + */ + protected Connection getSharedConnectionProxy(Connection target) { + List classes = new ArrayList(1); + classes.add(Connection.class); + return (Connection) Proxy.newProxyInstance( + Connection.class.getClassLoader(), + classes.toArray(new Class[classes.size()]), + new SharedConnectionInvocationHandler(target)); + } + + /** + * Invocation handler for a cached Rabbit Connection proxy. + */ + private class SharedConnectionInvocationHandler implements InvocationHandler { + + private final Connection target; + + public SharedConnectionInvocationHandler(Connection target) { + this.target = target; + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (method.getName().equals("equals")) { + // Only consider equal when proxies are identical. + return (proxy == args[0]); + } + else if (method.getName().equals("hashCode")) { + // Use hashCode of Connection proxy. + return System.identityHashCode(proxy); + } + else if (method.getName().equals("toString")) { + return "Shared Rabbit Connection: " + this.target; + } + //TODO investigate calling addShutdownListener + //TODO investigate calling of abort + else if (method.getName().equals("close")) { + // Handle close method: don't pass the call on. + return null; + } + else if (method.getName().equals("createChannel")) { + Integer channelNumber = null; + if (args != null && args.length == 1) { + channelNumber = (Integer) args[0]; + } + //TODO take into account channel number argument + if (channelNumber != null) { + logger.warn("Ignoring channel number argument when creating channel. To be fixed in the future."); + } + Channel channel = getChannel(this.target); + if (channel != null) { + return channel; + } + } + try { + Object retVal = method.invoke(this.target, args); + //TODO investigate exception listeners + return retVal; + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + } + + + + public class CachedChannelInvocationHandler implements InvocationHandler { + + private final Channel target; + + private final LinkedList channelList; + + + public CachedChannelInvocationHandler(Channel target, LinkedList channelList) { + this.target = target; + this.channelList = channelList; + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + String methodName = method.getName(); + if (methodName.equals("equals")) { + // Only consider equal when proxies are identical. + return (proxy == args[0]); + } + else if (methodName.equals("hashCode")) { + // Use hashCode of Channel proxy. + return System.identityHashCode(proxy); + } + else if (methodName.equals("toString")) { + return "Cached Rabbit Channel: " + this.target; + } + else if (methodName.equals("close")) { + // Handle close method: don't pass the call on. + if (active) { + synchronized (this.channelList) { + if (this.channelList.size() < getChannelCacheSize()) { + logicalClose((Channel) proxy); + // Remain open in the channel list. + return null; + } + } + } + + // If we get here, we're supposed to shut down. + physicalClose(); + return null; + } + else if (methodName.equals("getTargetChannel")) { + // Handle getTargetSession method: return underlying Channel. + return this.target; + } + try { + return method.invoke(this.target, args); + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + private void logicalClose(Channel proxy) throws Exception { + + // Allow for multiple close calls... + if (!this.channelList.contains(proxy)) { + if (logger.isTraceEnabled()) { + logger.trace("Returning cached Channel: " + this.target); + } + this.channelList.addLast(proxy); + } + } + + private void physicalClose() throws Exception { + if (logger.isDebugEnabled()) { + logger.debug("Closing cached Channel: " + this.target); + } + this.target.close(); + } + + } + + + + @Override + public String toString() { + return "CachingConnectionFactory [channelCacheSize=" + channelCacheSize + + ", hostName=" + hostName + ", portNumber=" + portNumber + + ", active=" + active + "]"; + } + +} diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/ChannelProxy.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/ChannelProxy.java new file mode 100644 index 00000000..5ee2b251 --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/ChannelProxy.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.connection; + +import com.rabbitmq.client.Channel; + +/** + * Subinterface of {@link com.rabbitmq.client.Channel} to be implemented by + * Channel proxies. Allows access to the underlying target Channel + * + * @author Mark Pollack + * @see CachingConnectionFactory + */ +public interface ChannelProxy extends Channel { + + /** + * Return the target Channel of this proxy. + *

This will typically be the native provider Channel + * @return the underlying Channel (never null) + */ + Channel getTargetChannel(); +} diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/ConnectionFactory.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/ConnectionFactory.java new file mode 100644 index 00000000..e1d259e7 --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/ConnectionFactory.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.connection; + +import java.io.IOException; + +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionParameters; + +/** + * An interface based ConnectionFactory for creating {@link com.rabbitmq.client.Connection}s. + * + *

NOTE: The Rabbit API contains a ConnectionFactory class (same name). + * + * @author Mark Fisher + */ +public interface ConnectionFactory { + + Connection createConnection() throws IOException; + + ConnectionParameters getParameters(); + + String getHostName(); +} diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/ConnectionFactoryUtils.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/ConnectionFactoryUtils.java new file mode 100644 index 00000000..e882779e --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/ConnectionFactoryUtils.java @@ -0,0 +1,266 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.connection; + +import java.io.IOException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.amqp.AmqpException; +import org.springframework.transaction.support.ResourceHolderSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.util.Assert; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; + +/** + * Helper class for managing a Spring based Rabbit {@link org.springframework.amqp.rabbit.connection.ConnectionFactory}, + * in particular for obtaining transactional Rabbit resources for a given ConnectionFactory. + * + *

Mainly for internal use within the framework. Used by {@link org.springframework.amqp.rabbit.core.RabbitTemplate} as + * well as {@link org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer}. + * + * @author Mark Fisher + */ +public class ConnectionFactoryUtils { + + private static final Log logger = LogFactory.getLog(ConnectionFactoryUtils.class); + + + /** + * Release the given Connection by closing it. + */ + public static void releaseConnection(Connection con, ConnectionFactory cf) { + if (con == null) { + return; + } + try { + con.close(); + } + catch (Throwable ex) { + logger.debug("Could not close RabbitMQ Connection", ex); + } + } + + /** + * Determine whether the given RabbitMQ Channel is transactional, that is, + * bound to the current thread by Spring's transaction facilities. + * @param channel the RabbitMQ Channel to check + * @param cf the RabbitMQ ConnectionFactory that the Channel originated from + * @return whether the Channel is transactional + */ + public static boolean isChannelTransactional(Channel channel, ConnectionFactory cf) { + if (channel == null || cf == null) { + return false; + } + RabbitResourceHolder resourceHolder = (RabbitResourceHolder) TransactionSynchronizationManager.getResource(cf); + return (resourceHolder != null && resourceHolder.containsChannel(channel)); + } + + /** + * Obtain a RabbitMQ Channel that is synchronized with the current transaction, if any. + * @param cf the ConnectionFactory to obtain a Channel for + * @param existingCon the existing RabbitMQ Connection to obtain a Channel for + * (may be null) + * @param synchedLocalTransactionAllowed whether to allow for a local RabbitMQ transaction + * that is synchronized with a Spring-managed transaction (where the main transaction + * might be a JDBC-based one for a specific DataSource, for example), with the RabbitMQ + * transaction committing right after the main transaction. If not allowed, the given + * ConnectionFactory needs to handle transaction enlistment underneath the covers. + * @return the transactional Channel, or null if none found + */ + public static Channel getTransactionalChannel( + final ConnectionFactory cf, final Connection existingCon, final boolean synchedLocalTransactionAllowed) throws IOException { + + return doGetTransactionalChannel(cf, new ResourceFactory() { + public Channel getChannel(RabbitResourceHolder holder) { + return holder.getChannel(Channel.class, existingCon); + } + public Connection getConnection(RabbitResourceHolder holder) { + return (existingCon != null ? existingCon : holder.getConnection()); + } + public Connection createConnection() throws IOException { + return cf.createConnection(); + } + public Channel createChannel(Connection con) throws IOException { + Channel channel = con.createChannel(); + if (synchedLocalTransactionAllowed) { + channel.txSelect(); + } + return channel; + } + public boolean isSynchedLocalTransactionAllowed() { + return synchedLocalTransactionAllowed; + } + }); + } + + /** + * Obtain a RabbitMQ Channel that is synchronized with the current transaction, if any. + * @param connectionFactory the RabbitMQ ConnectionFactory to bind for + * (used as TransactionSynchronizationManager key) + * @param resourceFactory the ResourceFactory to use for extracting or creating + * RabbitMQ resources + * @return the transactional Channel, or null if none found + */ + public static Channel doGetTransactionalChannel( + ConnectionFactory connectionFactory, ResourceFactory resourceFactory) throws IOException { + + Assert.notNull(connectionFactory, "ConnectionFactory must not be null"); + Assert.notNull(resourceFactory, "ResourceFactory must not be null"); + + RabbitResourceHolder resourceHolder = + (RabbitResourceHolder) TransactionSynchronizationManager.getResource(connectionFactory); + if (resourceHolder != null) { + Channel channel = resourceFactory.getChannel(resourceHolder); + if (channel != null) { + return channel; + } + if (resourceHolder.isFrozen()) { + return null; + } + } + if (!TransactionSynchronizationManager.isSynchronizationActive()) { + return null; + } + RabbitResourceHolder resourceHolderToUse = resourceHolder; + if (resourceHolderToUse == null) { + resourceHolderToUse = new RabbitResourceHolder(connectionFactory); + } + Connection con = resourceFactory.getConnection(resourceHolderToUse); + Channel channel = null; + try { + boolean isExistingCon = (con != null); + if (!isExistingCon) { + con = resourceFactory.createConnection(); + resourceHolderToUse.addConnection(con); + } + channel = resourceFactory.createChannel(con); + resourceHolderToUse.addChannel(channel, con); + } + catch (IOException ex) { + if (channel != null) { + try { + channel.close(); + } + catch (Throwable ex2) { + // ignore + } + } + if (con != null) { + try { + con.close(); + } + catch (Throwable ex2) { + // ignore + } + } + throw ex; + } + if (resourceHolderToUse != resourceHolder) { + TransactionSynchronizationManager.registerSynchronization( + new RabbitResourceSynchronization( + resourceHolderToUse, connectionFactory, resourceFactory.isSynchedLocalTransactionAllowed())); + resourceHolderToUse.setSynchronizedWithTransaction(true); + TransactionSynchronizationManager.bindResource(connectionFactory, resourceHolderToUse); + } + return channel; + } + + + /** + * Callback interface for resource creation. + * Serving as argument for the doGetTransactionalChannel method. + */ + public interface ResourceFactory { + + /** + * Fetch an appropriate Channel from the given RabbitResourceHolder. + * @param holder the RabbitResourceHolder + * @return an appropriate Channel fetched from the holder, + * or null if none found + */ + Channel getChannel(RabbitResourceHolder holder); + + /** + * Fetch an appropriate Connection from the given RabbitResourceHolder. + * @param holder the RabbitResourceHolder + * @return an appropriate Connection fetched from the holder, + * or null if none found + */ + Connection getConnection(RabbitResourceHolder holder); + + /** + * Create a new RabbitMQ Connection for registration with a RabbitResourceHolder. + * @return the new RabbitMQ Connection + * @throws IOException if thrown by RabbitMQ API methods + */ + Connection createConnection() throws IOException; + + /** + * Create a new RabbitMQ Session for registration with a RabbitResourceHolder. + * @param con the RabbitMQ Connection to create a Channel for + * @return the new RabbitMQ Channel + * @throws IOException if thrown by RabbitMQ API methods + */ + Channel createChannel(Connection con) throws IOException; + + /** + * Return whether to allow for a local RabbitMQ transaction that is synchronized with + * a Spring-managed transaction (where the main transaction might be a JDBC-based + * one for a specific DataSource, for example), with the RabbitMQ transaction + * committing right after the main transaction. + * @return whether to allow for synchronizing a local RabbitMQ transaction + */ + boolean isSynchedLocalTransactionAllowed(); + } + + + /** + * Callback for resource cleanup at the end of a non-native RabbitMQ transaction + * (e.g. when participating in a JtaTransactionManager transaction). + * @see org.springframework.transaction.jta.JtaTransactionManager + */ + private static class RabbitResourceSynchronization extends ResourceHolderSynchronization { + + private final boolean transacted; + + public RabbitResourceSynchronization(RabbitResourceHolder resourceHolder, Object resourceKey, boolean transacted) { + super(resourceHolder, resourceKey); + this.transacted = transacted; + } + + protected boolean shouldReleaseBeforeCompletion() { + return !this.transacted; + } + + protected void processResourceAfterCommit(RabbitResourceHolder resourceHolder) { + try { + resourceHolder.commitAll(); + } + catch (IOException e) { + throw new AmqpException("failed to commit RabbitMQ transaction", e); + } + } + + protected void releaseResource(RabbitResourceHolder resourceHolder, Object resourceKey) { + resourceHolder.closeAll(); + } + } + +} diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitResourceHolder.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitResourceHolder.java new file mode 100644 index 00000000..7a4be55c --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitResourceHolder.java @@ -0,0 +1,200 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.connection; + +import java.io.IOException; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.transaction.support.ResourceHolderSupport; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; + +/** + * Rabbit resource holder, wrapping a RabbitMQ Connection and Channel. + * RabbitTransactionManager binds instances of this class to the thread, + * for a given Rabbit ConnectionFactory. + * + *

Note: This is an SPI class, not intended to be used by applications. + * + * @author Mark Fisher + * @see RabbitTransactionManager (not yet implemented) + * @see RabbitTemplate + */ +public class RabbitResourceHolder extends ResourceHolderSupport { + + private static final Log logger = LogFactory.getLog(RabbitResourceHolder.class); + + + private ConnectionFactory connectionFactory; + + private boolean frozen = false; + + private final List connections = new LinkedList(); + + private final List channels = new LinkedList(); + + private final Map> channelsPerConnection = + new HashMap>(); + + + /** + * Create a new RabbitResourceHolder that is open for resources to be added. + * @see #addConnection + * @see #addChannel + */ + public RabbitResourceHolder() { + } + + /** + * Create a new RabbitResourceHolder that is open for resources to be added. + * @param connectionFactory the Rabbit ConnectionFactory that this + * resource holder is associated with (may be null) + */ + public RabbitResourceHolder(ConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + } + + /** + * Create a new RabbitResourceHolder for the given Rabbit Channel. + * @param channel the Rabbit Channel + */ + public RabbitResourceHolder(Channel channel) { + addChannel(channel); + this.frozen = true; + } + + /** + * Create a new RabbitResourceHolder for the given Rabbit resources. + * @param connection the Rabbit Connection + * @param channel the Rabbit Channel + */ + public RabbitResourceHolder(Connection connection, Channel channel) { + addConnection(connection); + addChannel(channel, connection); + this.frozen = true; + } + + /** + * Create a new RabbitResourceHolder for the given Rabbit resources. + * @param connectionFactory the Rabbit ConnectionFactory that this + * resource holder is associated with (may be null) + * @param connection the Rabbit Connection + * @param channel the Rabbit Channel + */ + public RabbitResourceHolder(ConnectionFactory connectionFactory, Connection connection, Channel channel) { + this.connectionFactory = connectionFactory; + addConnection(connection); + addChannel(channel, connection); + this.frozen = true; + } + + + public final boolean isFrozen() { + return this.frozen; + } + + public final void addConnection(Connection connection) { + Assert.isTrue(!this.frozen, "Cannot add Connection because RabbitResourceHolder is frozen"); + Assert.notNull(connection, "Connection must not be null"); + if (!this.connections.contains(connection)) { + this.connections.add(connection); + } + } + + public final void addChannel(Channel channel) { + addChannel(channel, null); + } + + public final void addChannel(Channel channel, Connection connection) { + Assert.isTrue(!this.frozen, "Cannot add Channel because RabbitResourceHolder is frozen"); + Assert.notNull(channel, "Channel must not be null"); + if (!this.channels.contains(channel)) { + this.channels.add(channel); + if (connection != null) { + List channels = this.channelsPerConnection.get(connection); + if (channels == null) { + channels = new LinkedList(); + this.channelsPerConnection.put(connection, channels); + } + channels.add(channel); + } + } + } + + public boolean containsChannel(Channel channel) { + return this.channels.contains(channel); + } + + + public Connection getConnection() { + return (!this.connections.isEmpty() ? this.connections.get(0) : null); + } + + public Connection getConnection(Class connectionType) { + return CollectionUtils.findValueOfType(this.connections, connectionType); + } + + public Channel getChannel() { + return (!this.channels.isEmpty() ? this.channels.get(0) : null); + } + + public Channel getChannel(Class channelType) { + return getChannel(channelType, null); + } + + public Channel getChannel(Class channelType, Connection connection) { + List channels = this.channels; + if (connection != null) { + channels = this.channelsPerConnection.get(connection); + } + return CollectionUtils.findValueOfType(channels, channelType); + } + + + public void commitAll() throws IOException { + for (Channel channel : this.channels) { + channel.txCommit(); + } + } + + public void closeAll() { + for (Channel channel : this.channels) { + try { + channel.close(); + } + catch (Throwable ex) { + logger.debug("Could not close synchronized Rabbit Channel after transaction", ex); + } + } + for (Connection con : this.connections) { + ConnectionFactoryUtils.releaseConnection(con, this.connectionFactory); + } + this.connections.clear(); + this.channels.clear(); + this.channelsPerConnection.clear(); + } + +} diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/ChannelAwareMessageListener.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/ChannelAwareMessageListener.java new file mode 100644 index 00000000..a1a45cd3 --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/ChannelAwareMessageListener.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.core; + +import org.springframework.amqp.core.Message; + +import com.rabbitmq.client.Channel; + +/** + * A message listener that is aware of the Channel on which the message was received. + * + * @author Mark Pollack + */ +public interface ChannelAwareMessageListener { + + /** + * Callback for processing a received Rabbit message. + *

Implementors are supposed to process the given Message, + * typically sending reply messages through the given Session. + * @param message the received AMQP message (never null) + * @param channel the underlying Rabbit Channel (never null) + * @throws Exception + */ + void onMessage(M message, Channel channel) throws Exception; + +} diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/ChannelCallback.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/ChannelCallback.java new file mode 100644 index 00000000..9402b876 --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/ChannelCallback.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.core; + +import com.rabbitmq.client.Channel; + +/** + * Basic callback for use in RabbitTemplate + * @author Mark Fisher + */ +public interface ChannelCallback { + + /** + * Execute any number of operations against the supplied RabbitMQ + * {@link Channel}, possibly returning a result. + */ + T doInRabbit(Channel channel) throws Exception; //Not sure what else Rabbit Throws.. + +} diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitMessageProperties.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitMessageProperties.java new file mode 100644 index 00000000..c3dc18e1 --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitMessageProperties.java @@ -0,0 +1,374 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.core; + +import java.io.UnsupportedEncodingException; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.amqp.AmqpUnsupportedEncodingException; +import org.springframework.amqp.core.Address; +import org.springframework.amqp.core.MessageDeliveryMode; +import org.springframework.amqp.core.MessageProperties; + +import com.rabbitmq.client.AMQP.BasicProperties; + +/** + * Rabbit implementation of MessageProperties that stores much of the message property + * information in Rabbit's {@link BasicProperites} class. Empty headers will be created on + * demand if they are null in the underlying BasicProperties instance. + * + * @author Mark Pollack + * @author Mark Fisher + */ +public class RabbitMessageProperties implements MessageProperties { + + private static final String DEFAULT_CHARSET = "UTF-8"; + + private volatile String defaultCharset = DEFAULT_CHARSET; + + private volatile String receivedExchange; + + private volatile String routingKey; + + private volatile Boolean redelivered; + + private volatile long deliveryTag; + + private volatile int messageCount; + + private volatile long contentLength; + + private final BasicProperties basicProperties; + + public RabbitMessageProperties() { + //TODO do we want this to be the default? + this.basicProperties = new BasicProperties("application/octet-stream", + null, + null, + 2, + 0, null, null, null, + null, null, null, null, + null, null); + initializeHeadersIfNecessary(basicProperties); + } + + /** + * @param basicProperties + * @param receivedExchange + * @param receivedRoutingKey + * @param redelivered + * @param deliveryTag + * @param messageCount + */ + public RabbitMessageProperties(BasicProperties basicProperties, String receivedExchange, + String receivedRoutingKey, Boolean redelivered, long deliveryTag, + int messageCount) { + super(); + this.basicProperties = basicProperties; + this.receivedExchange = receivedExchange; + this.routingKey = receivedRoutingKey; + this.redelivered = redelivered; + this.deliveryTag = deliveryTag; + this.messageCount = messageCount; + initializeHeadersIfNecessary(basicProperties); + } + + private void initializeHeadersIfNecessary(BasicProperties properties) { + if (properties.getHeaders() == null) { + properties.setHeaders(new HashMap()); + } + } + + /** + * Return Rabbit BasicProperties. + * @return the Rabbit BasicProperties + */ + public BasicProperties getBasicProperties() + { + return this.basicProperties; + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#getHeaders() + */ + public Map getHeaders() { + return basicProperties.getHeaders(); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#setHeader(java.lang.String, java.lang.Object) + */ + public void setHeader(String key, Object value) { + basicProperties.getHeaders().put(key, value); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#getTimestamp() + */ + public Date getTimestamp() { + return this.basicProperties.getTimestamp(); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#setAppId(java.lang.String) + */ + public void setAppId(String appId) { + basicProperties.setAppId(appId); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#getAppId() + */ + public String getAppId() { + return basicProperties.getAppId(); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#setUserId(java.lang.String) + */ + public void setUserId(String userId) { + basicProperties.setUserId(userId); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#getUserId() + */ + public String getUserId() { + return basicProperties.getUserId(); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#setType(java.lang.String) + */ + public void setType(String type) + { + basicProperties.setType(type); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#getType() + */ + public String getType() { + return basicProperties.getType(); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#setMessageId(java.lang.String) + */ + public void setMessageId(String id) + { + basicProperties.setMessageId(id); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#getMessageId() + */ + public String getMessageId() + { + return basicProperties.getMessageId(); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#setClusterId(java.lang.String) + */ + public void setClusterId(String id) + { + basicProperties.setClusterId(id); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#getClusterId() + */ + public String getClusterId() + { + return basicProperties.getClusterId(); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#setCorrelationId(byte[]) + */ + public void setCorrelationId(byte[] correlationId) { + //TODO this isn't exactly correct... + try { + basicProperties.setCorrelationId(new String(correlationId, this.defaultCharset)); + } + catch (UnsupportedEncodingException e) { + throw new AmqpUnsupportedEncodingException(e); + } + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#getCorrelationId() + */ + public byte[] getCorrelationId() { + if (basicProperties.getCorrelationId() == null) { + return null; + } + try { + return basicProperties.getCorrelationId().getBytes(this.defaultCharset); + } + catch (UnsupportedEncodingException ex) { + throw new AmqpUnsupportedEncodingException((UnsupportedEncodingException) ex); + } + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#setReplyTo(java.lang.String) + */ + public void setReplyTo(Address replyTo) { + basicProperties.setReplyTo(replyTo.toString()); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#getReplyTo() + */ + public Address getReplyTo() { + return Address.parse(basicProperties.getReplyTo()); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#setContentType(java.lang.String) + */ + public void setContentType(String contentType) { + basicProperties.setContentType(contentType); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#getContentType() + */ + public String getContentType() { + return basicProperties.getContentType(); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#setContentEncoding(java.lang.String) + */ + public void setContentEncoding(String contentEncoding) { + basicProperties.setContentEncoding(contentEncoding); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#getContentEncoding() + */ + public String getContentEncoding() { + return basicProperties.getContentEncoding(); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#setContentLength(long) + */ + public void setContentLength(long contentLength) { + this.contentLength = contentLength; + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#getContentLength() + */ + public long getContentLength() { + + return this.contentLength; + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#setDefaultCharset(java.lang.String) + */ + public void setDefaultCharset(String charSet) { + this.defaultCharset = (defaultCharset != null) ? defaultCharset : DEFAULT_CHARSET; + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#setDeliveryMode(java.lang.Integer) + */ + public void setDeliveryMode(MessageDeliveryMode deliveryMode) { + + this.basicProperties.setDeliveryMode(MessageDeliveryMode.toInt(deliveryMode)); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#getDeliveryMode() + */ + public MessageDeliveryMode getDeliveryMode() { + int deliveryMode = this.basicProperties.getDeliveryMode().intValue(); + return MessageDeliveryMode.fromInt(deliveryMode); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#setExpiration(java.lang.String) + */ + public void setExpiration(String expiration) { + this.basicProperties.setExpiration(expiration); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#getExpiration() + */ + public String getExpiration() { + return this.basicProperties.getExpiration(); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#setPriority(java.lang.Integer) + */ + public void setPriority(Integer priority) { + this.basicProperties.setPriority(priority); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#getPriority() + */ + public Integer getPriority() { + return this.basicProperties.getPriority(); + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#getReceivedExchange() + */ + public String getReceivedExchange() { + return this.receivedExchange; + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#getReceivedRoutingKey() + */ + public String getReceivedRoutingKey() { + return this.routingKey; + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#isRedelivered() + */ + public Boolean isRedelivered() { + return this.redelivered; + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#getDeliveryTag() + */ + public long getDeliveryTag() { + return this.deliveryTag; + } + + /* (non-Javadoc) + * @see org.springframework.amqp.core.MessageProperties#getMessageCount() + */ + public Integer getMessageCount() { + return this.messageCount; + } + +} diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitOperations.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitOperations.java new file mode 100644 index 00000000..777df0ae --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitOperations.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.core; + +import org.springframework.amqp.AmqpException; +import org.springframework.amqp.core.AmqpTemplate; + +/** + * Rabbit specific methods for Amqp functionality. + * @author Mark Pollack + * @author Mark Fisher + */ +public interface RabbitOperations extends AmqpTemplate { + + T execute(ChannelCallback action) throws AmqpException; + +} diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitTemplate.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitTemplate.java new file mode 100644 index 00000000..0cc70845 --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitTemplate.java @@ -0,0 +1,399 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.core; + +import java.io.IOException; + +import org.springframework.amqp.AmqpException; +import org.springframework.amqp.AmqpIllegalStateException; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageCreator; +import org.springframework.amqp.core.MessagePostProcessor; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils; +import org.springframework.amqp.rabbit.connection.RabbitResourceHolder; +import org.springframework.amqp.rabbit.support.RabbitAccessor; +import org.springframework.amqp.rabbit.support.RabbitUtils; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.amqp.support.converter.SimpleMessageConverter; +import org.springframework.util.Assert; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.GetResponse; + +/** + * Helper class that simplifies synchronous RabbitMQ access code. + * + * @author Mark Pollack + * @author Mark Fisher + */ +public class RabbitTemplate extends RabbitAccessor implements RabbitOperations { + + private static final String DEFAULT_EXCHANGE = ""; // alias for amq.direct default exchange + + private static final String DEFAULT_ROUTING_KEY = ""; + + + // TODO configure defaults + // void basicQos(int prefetchSize, int prefetchCount, boolean global) + + private volatile String defaultExchange = DEFAULT_EXCHANGE; + + private volatile String defaultRoutingKey = DEFAULT_ROUTING_KEY; + + // The default queue name that will be used for synchronous receives. + private volatile String defaultReceiveQueueName; + + private volatile boolean mandatoryPublish; + + private volatile boolean immediatePublish; + + private volatile boolean requireAckOnReceive = false; + + + /** + * Internal ResourceFactory adapter for interacting with + * ConnectionFactoryUtils + */ + private final RabbitTemplateResourceFactory transactionalResourceFactory = new RabbitTemplateResourceFactory(); + + private volatile MessageConverter messageConverter = new SimpleMessageConverter(); + + + public RabbitTemplate() { + initDefaultStrategies(); + } + + public RabbitTemplate(ConnectionFactory connectionFactory) { + this(); + setConnectionFactory(connectionFactory); + afterPropertiesSet(); + } + + + protected void initDefaultStrategies() { + setMessageConverter(new SimpleMessageConverter()); + } + + public void setDefaultExchange(String exchange) { + this.defaultExchange = (exchange != null) ? exchange : DEFAULT_EXCHANGE; + } + + public void setDefaultRoutingKey(String defaultRoutingKey) { + this.defaultRoutingKey = defaultRoutingKey; + } + + public void setDefaultReceiveQueueName(String defaultQueueName) { + this.defaultReceiveQueueName = defaultQueueName; + } + + public void setDefaultReceiveQueue(Queue defaultQueue) { + this.defaultReceiveQueueName = defaultQueue.getName(); + } + + public void setMandatoryPublish(boolean mandatoryPublish) { + this.mandatoryPublish = mandatoryPublish; + } + + public void setImmediatePublish(boolean immediatePublish) { + this.immediatePublish = immediatePublish; + } + + public void setRequireAckOnReceive(boolean requireAckOnReceive) { + this.requireAckOnReceive = requireAckOnReceive; + } + + /** + * Set the message converter for this template. Used to resolve Object + * parameters to convertAndSend methods and Object results from + * receiveAndConvert methods. + *

+ * The default converter is a SimpleMessageConverter, which is able to + * handle byte arrays, Strings, and Serializable Objects. + * + * @see #convertAndSend + * @see #receiveAndConvert + * @see org.springframework.amqp.support.converter.rabbit.support.converter.SimpleMessageConverter + */ + public void setMessageConverter(MessageConverter messageConverter) { + this.messageConverter = messageConverter; + } + + /** + * Return the message converter for this template. + */ + public MessageConverter getMessageConverter() { + return this.messageConverter; + } + + /** + * Fetch an appropriate Connection from the given RabbitResourceHolder. + * + * @param holder + * the RabbitResourceHolder + * @return an appropriate Connection fetched from the holder, or + * null if none found + */ + protected Connection getConnection(RabbitResourceHolder holder) { + return holder.getConnection(); + } + + /** + * Fetch an appropriate Channel from the given RabbitResourceHolder. + * + * @param holder + * the RabbitResourceHolder + * @return an appropriate Channel fetched from the holder, or + * null if none found + */ + protected Channel getChannel(RabbitResourceHolder holder) { + return holder.getChannel(); + } + + public void send(MessageCreator messageCreator) throws AmqpException { + send(this.defaultExchange, this.defaultRoutingKey, messageCreator); + } + + public void send(String routingKey, MessageCreator messageCreator) throws AmqpException { + send(this.defaultExchange, routingKey, messageCreator); + } + + public void send(final String exchange, final String routingKey, final MessageCreator messageCreator) throws AmqpException { + execute(new ChannelCallback() { + public Object doInRabbit(Channel channel) throws Exception { + doSend(channel, exchange, routingKey, messageCreator); + return null; + } + }); + } + + public void convertAndSend(Object object) throws AmqpException { + convertAndSend(this.defaultExchange, this.defaultRoutingKey, object); + } + + public void convertAndSend(String routingKey, final Object object) throws AmqpException { + convertAndSend(this.defaultExchange, routingKey, object); + } + + public void convertAndSend(String exchange, String routingKey, final Object object) throws AmqpException { + send(exchange, routingKey, new MessageCreator() { + public Message createMessage() { + return getRequiredMessageConverter().toMessage(object, new RabbitMessageProperties()); + } + }); + } + + public void convertAndSend(Object message, MessagePostProcessor messagePostProcessor) throws AmqpException { + convertAndSend(this.defaultExchange, this.defaultRoutingKey, message, messagePostProcessor); + } + + public void convertAndSend(String routingKey, Object message, MessagePostProcessor messagePostProcessor) throws AmqpException { + convertAndSend(this.defaultExchange, routingKey, message, messagePostProcessor); + } + + public void convertAndSend(String exchange, String routingKey, + final Object message, + final MessagePostProcessor messagePostProcessor) + throws AmqpException { + send(exchange, routingKey, new MessageCreator() { + public Message createMessage() { + Message msg = getRequiredMessageConverter().toMessage(message, new RabbitMessageProperties()); + return messagePostProcessor.postProcessMessage(msg); + } + }); + } + + public Message receive() throws AmqpException { + String queueName = getRequiredDefaultQueueName(); + return this.receive(queueName); + } + + public Message receive(final String queueName) { + return execute(new ChannelCallback() { + public Message doInRabbit(Channel channel) throws IOException { + GetResponse response = channel.basicGet(queueName, !requireAckOnReceive); + //TODO - check for null of response - got it when sending from .NET client, investigate + if (response != null) { + MessageProperties messageProps = new RabbitMessageProperties(response.getProps(), + response.getEnvelope().getExchange(), + response.getEnvelope().getRoutingKey(), + response.getEnvelope().isRedeliver(), + response.getEnvelope().getDeliveryTag(), + response.getMessageCount()); + return new Message(response.getBody(), messageProps); + } + return null; + } + }); + } + + public Object receiveAndConvert() throws AmqpException { + return receiveAndConvert(getRequiredDefaultQueueName()); + } + + public Object receiveAndConvert(String queueName) throws AmqpException { + Message response = receive(queueName); + if (response != null) { + return getRequiredMessageConverter().fromMessage(response); + } + return null; + } + + public T execute(ChannelCallback action) { + Assert.notNull(action, "Callback object must not be null"); + Connection conToClose = null; + Channel channelToClose = null; + try { + Channel channelToUse = ConnectionFactoryUtils + .doGetTransactionalChannel(getConnectionFactory(), + this.transactionalResourceFactory); + if (channelToUse == null) { + conToClose = createConnection(); + channelToClose = createChannel(conToClose); + channelToUse = channelToClose; + } + if (logger.isDebugEnabled()) { + logger.debug("Executing callback on RabbitMQ Channel: " + channelToUse); + } + return action.doInRabbit(channelToUse); + } + catch (Exception ex) { + throw convertRabbitAccessException(ex); + } + finally { + RabbitUtils.closeChannel(channelToClose); + ConnectionFactoryUtils.releaseConnection(conToClose, getConnectionFactory()); + } + } + + + /** + * Send the given message to the specified exchange. + * + * @param channel + * the RabbitMQ Channel to operate within + * @param exchange + * the name of the RabbitMQ exchange to send to + * @param routingKey + * the routing key to + * @param messageCreator + * callback to create a Message + * @throws IOException + * if thrown by RabbitMQ API methods + */ + protected void doSend(Channel channel, + String exchange, String routingKey, MessageCreator messageCreator) + throws Exception { + Assert.notNull(messageCreator, "MessageCreator must not be null"); + try { + Message message = messageCreator.createMessage(); + if (logger.isDebugEnabled()) { + logger.debug("Publishing message on exchange [" + exchange + "], routingKey = [" + routingKey + "]"); + } + + if (exchange == null) { + // try to send to default exchange + exchange = this.defaultExchange; + } + + if (routingKey == null) { + // try to send to default routing key + routingKey = this.defaultRoutingKey; + } + //TODO parameterize out default encoding + channel.basicPublish(exchange, routingKey, + this.mandatoryPublish, this.immediatePublish, + RabbitUtils.extractBasicProperties(message, "UTF-8"), message.getBody()); + // Check commit - avoid commit call within a JTA transaction. + // TODO: should we be able to do (via wrapper) something like: + // channel.getTransacted()? + if (isChannelTransacted() && isChannelLocallyTransacted(channel)) { + // Transacted channel created by this template -> commit. + RabbitUtils.commitIfNecessary(channel); + } + } finally { + // RabbitUtils. .. nothing to do here? compared to + // session.closeMessageProducer(producer); + } + } + + /** + * Check whether the given Channel is locally transacted, that is, whether + * its transaction is managed by this template's Channel handling and not by + * an external transaction coordinator. + * + * @param channel + * the Channel to check + * @return whether the given Channel is locally transacted + * @see ConnectionFactoryUtils#isChannelTransactional + * @see #isChannelTransacted + */ + protected boolean isChannelLocallyTransacted(Channel channel) { + return isChannelTransacted() + && !ConnectionFactoryUtils.isChannelTransactional(channel, getConnectionFactory()); + } + + private MessageConverter getRequiredMessageConverter() throws IllegalStateException { + MessageConverter converter = this.getMessageConverter(); + if (converter == null) { + throw new AmqpIllegalStateException( + "No 'messageConverter' specified. Check configuration of RabbitTemplate."); + } + return converter; + } + + private String getRequiredDefaultQueueName() throws IllegalStateException { + String name = this.defaultReceiveQueueName; + if (name == null) { + throw new AmqpIllegalStateException( + "No 'defaultQueueName' specified. Check configuration of RabbitTemplate."); + } + return name; + } + + + /** + * ResourceFactory implementation that delegates to this template's + * protected callback methods. + */ + private class RabbitTemplateResourceFactory implements ConnectionFactoryUtils.ResourceFactory { + + public Connection getConnection(RabbitResourceHolder holder) { + return RabbitTemplate.this.getConnection(holder); + } + + public Channel getChannel(RabbitResourceHolder holder) { + return RabbitTemplate.this.getChannel(holder); + } + + public Connection createConnection() throws IOException { + return RabbitTemplate.this.createConnection(); + } + + public Channel createChannel(Connection con) throws IOException { + return RabbitTemplate.this.createChannel(con); + } + + public boolean isSynchedLocalTransactionAllowed() { + return RabbitTemplate.this.isChannelTransacted(); + } + } + +} diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/support/RabbitGatewaySupport.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/support/RabbitGatewaySupport.java new file mode 100644 index 00000000..78e00bd5 --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/support/RabbitGatewaySupport.java @@ -0,0 +1,113 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.core.support; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.InitializingBean; + +/** + * Convenient super class for application classes that need JMS access. + * + *

Requires a ConnectionFactory or a RabbitTemplate instance to be set. + * It will create its own RabbitTemplate if a ConnectionFactory is passed in. + * A custom RabbitTemplate instance can be created for a given ConnectionFactory + * through overriding the {@link #createRabbitTemplate} method. + * + * @author Mark Pollack + * @see #setConnectionFactory + * @see #setRabbitTemplate + * @see #createRabbitTemplate + * @see org.springframework.amqp.rabbit.core.RabbitTemplate + */ +public class RabbitGatewaySupport implements InitializingBean { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + private RabbitTemplate rabbitTemplate; + + /** + * Set the Rabbit connection factory to be used by the gateway. + * Will automatically create a RabbitTemplate for the given ConnectionFactory. + * @see #createRabbitTemplate + * @see #setConnectionFactory(org.springframework.amqp.rabbit.connection.ConnectionFactory) + * @param connectionFactory + */ + public final void setConnectionFactory(ConnectionFactory connectionFactory) { + this.rabbitTemplate = createRabbitTemplate(connectionFactory); + } + + /** + * Create a RabbitTemplate for the given ConnectionFactory. + * Only invoked if populating the gateway with a ConnectionFactory reference. + * @param connectionFactory the Rabbit ConnectionFactory to create a RabbitTemplate for + * @return the new RabbitTemplate instance + * @see #setConnectionFactory + */ + protected RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory) { + return new RabbitTemplate(connectionFactory); + } + + /** + * Return the Rabbit ConnectionFactory used by the gateway. + */ + public final ConnectionFactory getConnectionFactory() { + return (this.rabbitTemplate != null ? this.rabbitTemplate.getConnectionFactory() : null); + } + + /** + * Set the RabbitTemplate for the gateway. + * @param rabbitTemplate + * @see #setConnectionFactory(org.springframework.amqp.rabbit.connection.ConnectionFactory) + */ + public final void setRabbitTemplate(RabbitTemplate rabbitTemplate) { + this.rabbitTemplate = rabbitTemplate; + } + + /** + * Return the RabbitTemplate for the gateway. + */ + public final RabbitTemplate getRabbitTemplate() { + return this.rabbitTemplate; + } + + public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException { + if (this.rabbitTemplate == null) { + throw new IllegalArgumentException("'connectionFactory' or 'rabbitTemplate' is required"); + } + try { + initGateway(); + } + catch (Exception ex) { + throw new BeanInitializationException("Initialization of Rabbit gateway failed: " + ex.getMessage(), ex); + } + } + + /** + * Subclasses can override this for custom initialization behavior. + * Gets called after population of this instance's bean properties. + * @throws java.lang.Exception if initialization fails + */ + protected void initGateway() throws Exception { + } + +} diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/AbstractMessageListenerContainer.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/AbstractMessageListenerContainer.java new file mode 100644 index 00000000..5c52fb40 --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/AbstractMessageListenerContainer.java @@ -0,0 +1,432 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.listener; + +import java.io.IOException; + +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageListener; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener; +import org.springframework.amqp.rabbit.support.RabbitUtils; +import org.springframework.util.Assert; +import org.springframework.util.ErrorHandler; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; + +/** + * @author Mark Pollack + * @author Mark Fisher + */ +public abstract class AbstractMessageListenerContainer extends AbstractRabbitListeningContainer { + + private volatile String queueName; + + private ErrorHandler errorHandler; + + private boolean exposeListenerChannel = true; + + private volatile Object messageListener; + + protected boolean autoAck = false; + + + /** + * Set the name of the queue to receive messages from. + * @param queueName the desired queue (can not be null) + */ + public void setQueueName(String queueName) { + //TODO change to QueueNames(String... queueNames) + Assert.notNull(queueName, "'queueName' must not be null"); + this.queueName = queueName; + } + + public void setQueues(Queue... queues) { + //TODO check for null arg value, refactor out of string based conventions. Merge with string queue name values? + StringBuilder sb = new StringBuilder(); + int size = queues.length; + for (int i=0; i < size; i++) { + sb.append(queues[i].getName()); + if (i != size-1) sb.append(","); + } + this.queueName = sb.toString(); + } + + /** + * Return the name of the queue to receive messages from. + */ + public String getQueueName() { + return this.queueName; + } + + protected String getRequiredQueueName() { + Assert.notNull(this.queueName, "Queue name must not be null."); + return this.queueName; + } + + public boolean isAutoAck() { + return this.autoAck; + } + + public void setAutoAck(boolean autoAck) { + this.autoAck = autoAck; + } + + /** + * Return whether to expose the listener {@link Channel} to a + * registered {@link ChannelAwareMessageListener}. + */ + public boolean isExposeListenerChannel() { + return this.exposeListenerChannel; + } + + /** + * Set whether to expose the listener Rabbit Channel to a registered + * {@link ChannelAwareMessageListener} as well as to + * {@link org.springframework.amqp.rabbit.core.RabbitTemplate} calls. + *

Default is "true", reusing the listener's {@link Channel}. + * Turn this off to expose a fresh Rabbit Channel fetched from the same + * underlying Rabbit {@link Connection} instead. + *

Note that Channels managed by an external transaction manager will + * always get exposed to {@link org.springframework.amqp.rabbit.core.RabbitTemplate} + * calls. So in terms of RabbitTemplate exposure, this setting only affects + * locally transacted Channels. + * @see ChannelAwareMessageListener + */ + public void setExposeListenerChannel(boolean exposeListenerChannel) { + this.exposeListenerChannel = exposeListenerChannel; + } + + /** + * Set the message listener implementation to register. + * This can be either a Spring {@link MessageListener} object + * or a Spring {@link ChannelAwareMessageListener} object. + * @throws IllegalArgumentException if the supplied listener is not a + * {@link MessageListener} or a {@link ChannelAwareMessageListener} + * @see MessageListener + * @see ChannelAwareMessageListener + */ + public void setMessageListener(Object messageListener) { + checkMessageListener(messageListener); + this.messageListener = messageListener; + } + + /** + * Check the given message listener, throwing an exception + * if it does not correspond to a supported listener type. + *

By default, only a Spring {@link MessageListener} object or a + * Spring {@link SessionAwareMessageListener} object will be accepted. + * @param messageListener the message listener object to check + * @throws IllegalArgumentException if the supplied listener is not a + * {@link MessageListener} or a {@link SessionAwareMessageListener} + * @see MessageListener + * @see ChannelAwareMessageListener + */ + protected void checkMessageListener(Object messageListener) { + if (!(messageListener instanceof MessageListener || + messageListener instanceof ChannelAwareMessageListener)) { + throw new IllegalArgumentException( + "Message listener needs to be of type [" + MessageListener.class.getName() + + "] or [" + ChannelAwareMessageListener.class.getName() + "]"); + } + } + + /** + * Return the message listener object to register. + */ + public Object getMessageListener() { + return this.messageListener; + } + + + + /** + * Set an ErrorHandler to be invoked in case of any uncaught exceptions thrown + * while processing a Message. By default there will be no ErrorHandler + * so that error-level logging is the only result. + */ + public void setErrorHandler(ErrorHandler errorHandler) { + this.errorHandler = errorHandler; + } + + + /** + * Invoke the registered ErrorHandler, if any. Log at error level otherwise. + * @param ex the uncaught error that arose during Rabbit processing. + * @see #setErrorHandler + */ + protected void invokeErrorHandler(Throwable ex) { + if (this.errorHandler != null) { + this.errorHandler.handleError(ex); + } + else if (logger.isWarnEnabled()) { + logger.warn("Execution of Rabbit message listener failed, and no ErrorHandler has been set.", ex); + } + } + + //------------------------------------------------------------------------- + // Template methods for listener execution + //------------------------------------------------------------------------- + + /** + * Execute the specified listener, + * committing or rolling back the transaction afterwards (if necessary). + * @param channel the Rabbit Channel to operate on + * @param message the received Rabbit Message + * @see #invokeListener + * @see #commitIfNecessary + * @see #rollbackOnExceptionIfNecessary + * @see #handleListenerException + */ + protected void executeListener(Channel channel, Message message) { + try { + doExecuteListener(channel, message); + } + catch (Throwable ex) { + handleListenerException(ex); + } + } + + + /** + * Execute the specified listener, + * committing or rolling back the transaction afterwards (if necessary). + * @param channel the Rabbit Channel to operate on + * @param message the received Rabbit Message + * @throws Throwable + * @throws Exception if thrown by Rabbit API methods + * @see #invokeListener + * @see #commitIfNecessary + * @see #rollbackOnExceptionIfNecessary + * @see #convertRabbitAccessException + */ + protected void doExecuteListener(Channel channel, Message message) throws Throwable { + //TODO consider adding support for AcceptMessagesWhileStopping functionality + if (!isRunning()) { + if (logger.isWarnEnabled()) { + logger.warn("Rejecting received message because of the listener container " + + "having been stopped in the meantime: " + message); + } + rollbackIfNecessary(channel); + throw new MessageRejectedWhileStoppingException(); + } + try { + invokeListener(channel, message); + } + catch (Throwable ex) { + rollbackOnExceptionIfNecessary(channel, ex); + throw ex; + } + commitIfNecessary(channel, message); + } + + /** + * Invoke the specified listener: either as standard MessageListener + * or (preferably) as SessionAwareMessageListener. + * @param channel the Rabbit Channel to operate on + * @param message the received Rabbit Message + * @throws Exception + * @throws Exception if thrown by Rabbit API methods + * @see #setMessageListener + */ + protected void invokeListener(Channel channel, Message message) throws Exception { + Object listener = getMessageListener(); + if (listener instanceof ChannelAwareMessageListener) { + doInvokeListener((ChannelAwareMessageListener) listener, channel, message); + } + else if (listener instanceof MessageListener) { + doInvokeListener((MessageListener) listener, message); + } + else if (listener != null) { + throw new IllegalArgumentException( + "Only MessageListener and SessionAwareMessageListener supported: " + listener); + } + else { + throw new IllegalStateException("No message listener specified - see property 'messageListener'"); + } + } + + /** + * Invoke the specified listener as Spring ChannelAwareMessageListener, + * exposing a new Rabbit Session (potentially with its own transaction) + * to the listener if demanded. + * @param listener the Spring ChannelAwareMessageListener to invoke + * @param session the Rabbit Channel to operate on + * @param message the received Rabbit Message + * @throws Exception if thrown by Rabbit API methods + * @see ChannelAwareMessageListener + * @see #setExposeListenerSession + */ + protected void doInvokeListener(ChannelAwareMessageListener listener, Channel channel, Message message) throws Exception { + + Connection conToClose = null; + Channel channelToClose = null; + try { + Channel channelToUse = channel; + if (!isExposeListenerChannel()) { + // We need to expose a separate Session. + conToClose = createConnection(); + channelToClose = createChannel(conToClose); + channelToUse = channelToClose; + } + // Actually invoke the message listener... + listener.onMessage(message, channelToUse); + + //TODO need to figure out tx usage more. + /* + // Clean up specially exposed Channel, if any. + if (channelToUse != channel) { + if (channelToUse.getTransacted() && isChannelLocallyTransacted(channelToUse)) { + // Transacted session created by this container -> commit. + RabbitUtils.commitIfNecessary(channelToUse); + } + }*/ + } + finally { + RabbitUtils.closeChannel(channelToClose); + RabbitUtils.closeConnection(conToClose); + } + } + + /** + * Invoke the specified listener as Spring Rabbit MessageListener. + *

Default implementation performs a plain invocation of the + * onMessage method. + * @param listener the Rabbit MessageListener to invoke + * @param message the received Rabbit Message + * @throws Exception if thrown by RAbbit API methods + * @see org.springframework.core.amq.MessageListener#onMessage + */ + protected void doInvokeListener(MessageListener listener, Message message) { + listener.onMessage(message); + } + + + + /** + * Perform a commit or message acknowledgement, as appropriate. + * @param channel the Rabbit channel to commit + * @param message the Message to acknowledge + * @throws Exception in case of commit failure + */ + protected void commitIfNecessary(Channel channel, Message message) { + + //TODO Should probably go to doExecuteListener() of base class. + if (!autoAck) { + if (channel.isOpen()) { + try { + channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); + } catch (IOException e) { + // TODO Auto-generated catch block + logger.warn("Could not ack message with delivery tag [" + message.getMessageProperties().getDeliveryTag() + "]", e); + } + } + } + if (this.isChannelLocallyTransacted(channel)) { + RabbitUtils.commitIfNecessary(channel); + } + } + + /** + * Perform a rollback, if appropriate. + * @param session the Rabbit Channel to rollback + * @throws Exception in case of a rollback error + */ + protected void rollbackIfNecessary(Channel channel) { + if (this.isChannelLocallyTransacted(channel)) { + // Transacted channel enabled by this container -> rollback. + RabbitUtils.rollbackIfNecessary(channel); + } + } + + /** + * Perform a rollback, handling rollback exceptions properly. + * @param session the Rabbit Channel to rollback + * @param ex the thrown application exception or error + * @throws Exception in case of a rollback error + */ + protected void rollbackOnExceptionIfNecessary(Channel channel, Throwable ex) throws Exception { + + //TODO not sure if the exception at this point if only from the application or from Rabbit. + try { + if (this.isChannelLocallyTransacted(channel)) { + if (logger.isDebugEnabled()) { + logger.debug("Initiating transaction rollback on application exception", ex); + } + RabbitUtils.rollbackIfNecessary(channel); + } + } catch (Exception ex2) + { + logger.error("Application exception overridden by rollback exception", ex); + throw ex2; + } + } + + /** + * Check whether the given Channel is locally transacted, that is, whether + * its transaction is managed by this listener container's Channel handling + * and not by an external transaction coordinator. + *

Note:This method is about finding out whether the Channel's transaction + * is local or externally coordinated. + * @param channel the Channel to check + * @return whether the given Channel is locally transacted + * @see #isChannelTransacted() + */ + protected boolean isChannelLocallyTransacted(Channel channel) { + return this.isChannelTransacted(); + } + + /** + * Handle the given exception that arose during listener execution. + *

The default implementation logs the exception at error level, + * not propagating it to the Rabbit provider - assuming that all handling of + * acknowledgment and/or transactions is done by this listener container. + * This can be overridden in subclasses. + * @param ex the exception to handle + */ + protected void handleListenerException(Throwable ex) { + if (ex instanceof MessageRejectedWhileStoppingException) { + // Internal exception - has been handled before. + return; + } + /* TODO how to handle exceptions that rabbit might throw + if (ex instanceof JMSException) { + invokeExceptionListener((JMSException) ex); + } + */ + if (isActive()) { + // Regular case: failed while active. + // Invoke ErrorHandler if available. + invokeErrorHandler(ex); + } + else { + // Rare case: listener thread failed after container shutdown. + // Log at debug level, to avoid spamming the shutdown log. + logger.debug("Listener exception after container shutdown", ex); + } + } + + + /** + * Internal exception class that indicates a rejected message on shutdown. + * Used to trigger a rollback for an external transaction manager in that case. + */ + private static class MessageRejectedWhileStoppingException extends RuntimeException { + + } +} diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/AbstractRabbitListeningContainer.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/AbstractRabbitListeningContainer.java new file mode 100644 index 00000000..f50562db --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/AbstractRabbitListeningContainer.java @@ -0,0 +1,427 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.listener; + + +import org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils; +import org.springframework.amqp.rabbit.support.RabbitAccessor; +import org.springframework.amqp.rabbit.support.RabbitUtils; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.context.SmartLifecycle; + +import com.rabbitmq.client.Connection; + +/** + * @author Mark Pollack + */ +public abstract class AbstractRabbitListeningContainer extends RabbitAccessor implements BeanNameAware, DisposableBean, SmartLifecycle { + + //TODO See if can replace methods with general throws Exception signature to use a more specific exception. + + private volatile String beanName; + + private volatile Connection sharedConnection; + + private volatile boolean autoStartup = true; + + private int phase = Integer.MAX_VALUE; + + private volatile boolean active = false; + + private volatile boolean running = false; + + protected final Object lifecycleMonitor = new Object(); + + + /** + * Set whether to automatically start the container after initialization. + *

Default is "true"; set this to "false" to allow for manual startup + * through the {@link #start()} method. + */ + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + + public boolean isAutoStartup() { + return this.autoStartup; + } + + /** + * Specify the phase in which this container should be started and + * stopped. The startup order proceeds from lowest to highest, and + * the shutdown order is the reverse of that. By default this value + * is Integer.MAX_VALUE meaning that this container starts as late + * as possible and stops as soon as possible. + */ + public void setPhase(int phase) { + this.phase = phase; + } + + /** + * Return the phase in which this container will be started and stopped. + */ + public int getPhase() { + return this.phase; + } + + public void setBeanName(String beanName) { + this.beanName = beanName; + } + + /** + * Return the bean name that this listener container has been assigned + * in its containing bean factory, if any. + */ + protected final String getBeanName() { + return this.beanName; + } + + /** + * Delegates to {@link #validateConfiguration()} and {@link #initialize()}. + */ + public void afterPropertiesSet() { + super.afterPropertiesSet(); + validateConfiguration(); + initialize(); + } + + /** + * Validate the configuration of this container. + *

The default implementation is empty. To be overridden in subclasses. + */ + protected void validateConfiguration() { + } + + /** + * Calls {@link #shutdown()} when the BeanFactory destroys the container instance. + * @see #shutdown() + */ + public void destroy() { + shutdown(); + } + + + //------------------------------------------------------------------------- + // Lifecycle methods for starting and stopping the container + //------------------------------------------------------------------------- + + /** + * Initialize this container. + *

Creates a Rabbit Connection and calls {@link #doInitialize()}. + */ + public void initialize() { + try { + synchronized (this.lifecycleMonitor) { + this.active = true; + this.lifecycleMonitor.notifyAll(); + } + doInitialize(); + } + catch (Exception ex) { + ConnectionFactoryUtils.releaseConnection(this.sharedConnection, getConnectionFactory()); + this.sharedConnection = null; + throw convertRabbitAccessException(ex); + } + } + + /** + * Stop the shared Connection, call {@link #doShutdown()}, + * and close this container. + */ + public void shutdown() { + logger.debug("Shutting down Rabbit listener container"); + boolean wasRunning = false; + synchronized (this.lifecycleMonitor) { + wasRunning = this.running; + this.running = false; + this.active = false; + this.lifecycleMonitor.notifyAll(); + } + + // Stop shared Connection early, if necessary. + if (wasRunning && sharedConnectionEnabled()) { + try { + stopSharedConnection(); + } + catch (Throwable ex) { + logger.debug("Could not stop Rabbit Connection on shutdown", ex); + } + } + + // Shut down the invokers. + try { + doShutdown(); + } + catch (Exception ex) { + throw convertRabbitAccessException(ex); + } + finally { + if (sharedConnectionEnabled()) { + ConnectionFactoryUtils.releaseConnection(this.sharedConnection, getConnectionFactory()); + this.sharedConnection = null; + } + } + } + + /** + * Return whether this container is currently active, + * that is, whether it has been set up but not shut down yet. + */ + public final boolean isActive() { + synchronized (this.lifecycleMonitor) { + return this.active; + } + } + + /** + * Start this container. + * @see #doStart + */ + public void start() { + try { + doStart(); + } + catch (Exception ex) { + throw convertRabbitAccessException(ex); + } + } + + /** + * Start the shared Connection, if any, and notify all invoker tasks. + * @throws Exception if thrown by Rabbit API methods + * @see #startSharedConnection + */ + protected void doStart() throws Exception { + // Lazily establish a shared Connection, if necessary. + if (sharedConnectionEnabled()) { + establishSharedConnection(); + } + + // Reschedule paused tasks, if any. + synchronized (this.lifecycleMonitor) { + this.running = true; + this.lifecycleMonitor.notifyAll(); + } + + } + + /** + * Stop this container. + * @see #doStop + */ + public void stop() { + try { + doStop(); + } + catch (Exception ex) { + throw convertRabbitAccessException(ex); + } + } + + public void stop(Runnable callback) { + this.stop(); + callback.run(); + } + + /** + * Notify all invoker tasks and stop the shared Connection, if any. + * @see #stopSharedConnection + */ + protected void doStop() { + synchronized (this.lifecycleMonitor) { + this.running = false; + this.lifecycleMonitor.notifyAll(); + } + if (sharedConnectionEnabled()) { + stopSharedConnection(); + } + } + + /** + * Determine whether this container is currently running, + * that is, whether it has been started and not stopped yet. + * @see #start() + * @see #stop() + * @see #runningAllowed() + */ + public final boolean isRunning() { + synchronized (this.lifecycleMonitor) { + return (this.running && runningAllowed()); + } + } + + /** + * Check whether this container's listeners are generally allowed to run. + *

This implementation always returns true; the default 'running' + * state is purely determined by {@link #start()} / {@link #stop()}. + *

Subclasses may override this method to check against temporary + * conditions that prevent listeners from actually running. In other words, + * they may apply further restrictions to the 'running' state, returning + * false if such a restriction prevents listeners from running. + */ + protected boolean runningAllowed() { + return true; + } + + + //------------------------------------------------------------------------- + // Management of a shared Rabbit Connection + //------------------------------------------------------------------------- + + /** + * Establish a shared Connection for this container. + *

The default implementation delegates to {@link #createSharedConnection()}, + * which does one immediate attempt and throws an exception if it fails. + * Can be overridden to have a recovery process in place, retrying + * until a Connection can be successfully established. + * @throws Exception if thrown by Rabbit API methods + */ + protected void establishSharedConnection() throws Exception { + if (this.sharedConnection == null) { + this.sharedConnection = createSharedConnection(); + logger.debug("Established shared Rabbit Connection"); + } + } + + /** + * Refresh the shared Connection that this container holds. + *

Called on startup and also after an infrastructure exception + * that occurred during invoker setup and/or execution. + * @throws Exception if thrown by Rabbit API methods + */ + protected final void refreshSharedConnection() throws Exception { + ConnectionFactoryUtils.releaseConnection( + this.sharedConnection, getConnectionFactory()); + this.sharedConnection = null; + this.sharedConnection = createSharedConnection(); + } + + /** + * Create a shared Connection for this container. + *

The default implementation creates a standard Connection + * and prepares it through {@link #prepareSharedConnection}. + * @return the prepared Connection + * @throws Exception if the creation failed + */ + protected Connection createSharedConnection() throws Exception { + Connection con = createConnection(); + try { + prepareSharedConnection(con); + return con; + } + catch (Exception ex) { + RabbitUtils.closeConnection(con); + throw ex; + } + } + + /** + * Prepare the given Connection, which is about to be registered + * as shared Connection for this container. + *

The default implementation sets the specified client id, if any. + * Subclasses can override this to apply further settings. + * @param connection the Connection to prepare + */ + protected void prepareSharedConnection(Connection connection) { + } + + /** + * Stop the shared Connection. + * @throws Exception if thrown by Rabbit API methods + */ + protected void stopSharedConnection() { + if (this.sharedConnection != null) { + try { + this.sharedConnection.close(); + } + catch (Exception ex) { + logger.debug("Ignoring Connection close exception - assuming already closed: " + ex); + } + } + } + + /** + * Return the shared Rabbit Connection maintained by this container. + * Available after initialization. + * @return the shared Connection (never null) + * @throws IllegalStateException if this container does not maintain a + * shared Connection, or if the Connection hasn't been initialized yet + * @see #sharedConnectionEnabled() + */ + protected final Connection getSharedConnection() { + if (!sharedConnectionEnabled()) { + throw new IllegalStateException( + "This listener container does not maintain a shared Connection"); + } + if (this.sharedConnection == null) { + throw new SharedConnectionNotInitializedException( + "This listener container's shared Connection has not been initialized yet"); + } + return this.sharedConnection; + } + + + //------------------------------------------------------------------------- + // Template methods to be implemented by subclasses + //------------------------------------------------------------------------- + + /** + * Return whether a shared Rabbit Connection should be maintained + * by this container base class. + * @see #getSharedConnection() + */ + protected abstract boolean sharedConnectionEnabled(); + + /** + * Register any invokers within this container. + *

Subclasses need to implement this method for their specific + * invoker management process. + *

A shared Rabbit Connection + * @throws Exception + * @see #getSharedConnection() + */ + protected abstract void doInitialize() throws Exception; + + /** + * Close the registered invokers. + *

Subclasses need to implement this method for their specific + * invoker management process. + *

A shared Rabbit Connection, if any, will automatically be closed + * afterwards. + * @see #shutdown() + */ + protected abstract void doShutdown(); + + + /** + * Exception that indicates that the initial setup of this container's + * shared Rabbit Connection failed. This is indicating to invokers that they need + * to establish the shared Connection themselves on first access. + */ + public static class SharedConnectionNotInitializedException extends RuntimeException { + + /** + * Create a new SharedConnectionNotInitializedException. + * @param msg the detail message + */ + protected SharedConnectionNotInitializedException(String msg) { + super(msg); + } + } + +} diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/BlockingQueueConsumer.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/BlockingQueueConsumer.java new file mode 100644 index 00000000..3f8d582c --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/BlockingQueueConsumer.java @@ -0,0 +1,171 @@ +package org.springframework.amqp.rabbit.listener; + +import java.io.IOException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.DefaultConsumer; +import com.rabbitmq.client.Envelope; +import com.rabbitmq.client.ShutdownSignalException; +import com.rabbitmq.client.AMQP.BasicProperties; +import com.rabbitmq.utility.Utility; +/** + * Variation on QueueingConsumer in RabbitMQ, uses 'put' instead of 'add' and stored a reference to the consumerTag that + * was returned when this Consumer was registered with the channel so as to make it easy to close the consumer when shutting down. + * @author Mark Pollack + * + */ +public class BlockingQueueConsumer extends DefaultConsumer { + + private String _consumerTag; + + private final BlockingQueue _queue; + + // When this is non-null the queue is in shutdown mode and nextDelivery should + // throw a shutdown signal exception. + private volatile ShutdownSignalException _shutdown; + + // Marker object used to signal the queue is in shutdown mode. + // It is only there to wake up consumers. The canonical representation + // of shutting down is the presence of _shutdown. + // Invariant: This is never on _queue unless _shutdown != null. + private static final Delivery POISON = new Delivery(null, null, null); + + public BlockingQueueConsumer(Channel ch) { + this(ch, new LinkedBlockingQueue()); + } + + public BlockingQueueConsumer(Channel ch, BlockingQueue q) + { + super(ch); + this._queue = q; + } + + + + public String getConsumerTag() { + return _consumerTag; + } + + public void setConsumerTag(String consumerTag) + { + _consumerTag = consumerTag; + } + + @Override public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) { + _shutdown = sig; + try { + _queue.put(POISON); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + @Override public void handleDelivery(String consumerTag, + Envelope envelope, + AMQP.BasicProperties properties, + byte[] body) + throws IOException + { + //TODO do we want to pass on 'consumerTag'? + checkShutdown(); + try { + this._queue.put(new Delivery(envelope, properties, body)); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + /** + * Encapsulates an arbitrary message - simple "bean" holder structure. + */ + public static class Delivery { + private final Envelope _envelope; + private final AMQP.BasicProperties _properties; + private final byte[] _body; + + public Delivery(Envelope envelope, AMQP.BasicProperties properties, byte[] body) { + _envelope = envelope; + _properties = properties; + _body = body; + } + + /** + * Retrieve the message envelope. + * @return the message envelope + */ + public Envelope getEnvelope() { + return _envelope; + } + + /** + * Retrieve the message properties. + * @return the message properties + */ + public BasicProperties getProperties() { + return _properties; + } + + /** + * Retrieve the message body. + * @return the message body + */ + public byte[] getBody() { + return _body; + } + } + + /** + * Check if we are in shutdown mode and if so throw an exception. + */ + private void checkShutdown(){ + if(_shutdown != null) throw Utility.fixStackTrace(_shutdown); + } + + /** + * If this is a non-POISON non-null delivery simply return it. + * If this is POISON we are in shutdown mode, throw _shutdown + * If this is null, we may be in shutdown mode. Check and see. + * @throws InterruptedException + */ + private Delivery handle(Delivery delivery) throws InterruptedException + { + if(delivery == POISON || (delivery == null && _shutdown != null)){ + if(delivery == POISON) _queue.put(POISON); + throw Utility.fixStackTrace(_shutdown); + } + return delivery; + } + + /** + * Main application-side API: wait for the next message delivery and return it. + * @return the next message + * @throws InterruptedException if an interrupt is received while waiting + * @throws ShutdownSignalException if the connection is shut down while waiting + */ + public Delivery nextDelivery() + throws InterruptedException, ShutdownSignalException + { + return handle(_queue.take()); + } + + /** + * Main application-side API: wait for the next message delivery and return it. + * @param timeout timeout in millisecond + * @return the next message or null if timed out + * @throws InterruptedException if an interrupt is received while waiting + * @throws ShutdownSignalException if the connection is shut down while waiting + */ + public Delivery nextDelivery(long timeout) + throws InterruptedException, ShutdownSignalException + { + checkShutdown(); + return handle(_queue.poll(timeout, TimeUnit.MILLISECONDS)); + } + +} diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/LocallyExposedRabbitResourceHolder.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/LocallyExposedRabbitResourceHolder.java new file mode 100644 index 00000000..ad8119a5 --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/LocallyExposedRabbitResourceHolder.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.listener; + +import com.rabbitmq.client.Channel; + +/** + * + * @author Mark Fisher + * + */ +public class LocallyExposedRabbitResourceHolder extends org.springframework.amqp.rabbit.connection.RabbitResourceHolder { + + public LocallyExposedRabbitResourceHolder(Channel channel) { + super(channel); + } +} diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/SimpleMessageListenerContainer.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/SimpleMessageListenerContainer.java new file mode 100644 index 00000000..221269b4 --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/SimpleMessageListenerContainer.java @@ -0,0 +1,313 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.listener; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingQueue; + +import org.springframework.amqp.AmqpException; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitMessageProperties; +import org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.Delivery; +import org.springframework.amqp.rabbit.support.RabbitUtils; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.Envelope; +import com.rabbitmq.client.ShutdownSignalException; + +/** + * @author Mark Pollack + * @author Mark Fisher + */ +public class SimpleMessageListenerContainer extends AbstractMessageListenerContainer { + + private volatile int prefetchCount = 1; + + private volatile Executor taskExecutor = new SimpleAsyncTaskExecutor(); + + private volatile int concurrentConsumers = 1; + + private volatile int blockingQueueConsumerCapacity = -1; //implied unlimited capacity + + private volatile Set channels = null; + + private volatile Set consumers; + + private final Object consumersMonitor = new Object(); + + + public SimpleMessageListenerContainer() { + + } + + public SimpleMessageListenerContainer(ConnectionFactory connectionFactory) { + this.setConnectionFactory(connectionFactory); + } + + /** + * Specify the number of concurrent consumers to create. Default is 1. + *

Raising the number of concurrent consumers is recommendable in order + * to scale the consumption of messages coming in from a queue. However, + * note that any ordering guarantees are lost once multiple consumers are + * registered. In general, stick with 1 consumer for low-volume queues. + */ + public void setConcurrentConsumers(int concurrentConsumers) { + Assert.isTrue(concurrentConsumers > 0, "'concurrentConsumers' value must be at least 1 (one)"); + this.concurrentConsumers = concurrentConsumers; + } + + /** + * Avoid the possibility of not configuring the CachingConnectionFactory in sync with the + * number of concurrent consumers. + */ + public void afterPropertiesSet() { + super.afterPropertiesSet(); + if (this.getConnectionFactory() instanceof CachingConnectionFactory) { + CachingConnectionFactory cf = (CachingConnectionFactory) getConnectionFactory(); + if (cf.getChannelCacheSize() < this.concurrentConsumers ) { + throw new IllegalStateException("CachingConnectionFactory's ChannelCacheSize not be less than the number of SimpleMessageListener's ConcurrentConsumers"); + } + //Default setting + if (concurrentConsumers == 1) { + //Set concurrent consumers to size of connection factory channel cache. + if (cf.getChannelCacheSize() > 1) { + logger.info("Setting number of concurrent consumers to CachingConnectionFactory's ChannelCacheSize [" + cf.getChannelCacheSize() + "]"); + this.concurrentConsumers = cf.getChannelCacheSize(); + } + } + } + } + + + public void setTaskExecutor(Executor taskExecutor) { + Assert.notNull(taskExecutor, "taskExecutor must not be null"); + this.taskExecutor = taskExecutor; + } + + public int getPrefetchCount() { + return prefetchCount; + } + + public void setPrefetchCount(int prefetchCount) { + this.prefetchCount = prefetchCount; + } + + public int getBlockingQueueConsumerCapacity() { + return blockingQueueConsumerCapacity; + } + + public void setBlockingQueueConsumerCapacity(int blockingQueueConsumerCapacity) { + this.blockingQueueConsumerCapacity = blockingQueueConsumerCapacity; + } + + //------------------------------------------------------------------------- + // Implementation of AbstractMessageListenerContainer's template methods + //------------------------------------------------------------------------- + + /** + * Always use a shared Rabbit Connection. + */ + protected final boolean sharedConnectionEnabled() { + return true; + } + + /** + * Creates the specified number of concurrent consumers, + * in the form of a Rabbit Channel plus associated MessageConsumer. + * @throws Exception + */ + protected void doInitialize() throws Exception { + establishSharedConnection(); + initializeConsumers(); + } + + /** + * Re-initializes this container's Rabbit message consumers, + * if not initialized already. Then submits each consumer to + * this container's task executor. + * @throws Exception + */ + protected void doStart() throws Exception { + super.doStart(); + initializeConsumers(); + for (BlockingQueueConsumer consumer : this.consumers) { + this.taskExecutor.execute(new AsyncMessageProcessingConsumer(consumer, 0, 0, this)); + } + } + + + @Override + protected void doShutdown() { + if (!this.isRunning()) { + return; + } + logger.debug("Closing Rabbit Consumers"); + for (BlockingQueueConsumer consumer : this.consumers) { + RabbitUtils.closeMessageConsumer(consumer.getChannel(), consumer.getConsumerTag()); + } + logger.debug("Closing Rabbit Channels"); + for (Channel channel : this.channels) { + RabbitUtils.closeChannel(channel); + } + } + + + protected void initializeConsumers() throws IOException { + synchronized (this.consumersMonitor) { + if (this.consumers == null) { + this.channels = new HashSet(this.concurrentConsumers); + this.consumers = new HashSet(this.concurrentConsumers); + Connection con = getSharedConnection(); + for (int i = 0; i < this.concurrentConsumers; i++) { + Channel channel = createChannel(con); + if (this.isChannelLocallyTransacted(channel)) { + channel.txSelect(); + } + BlockingQueueConsumer consumer = createBlockingQueueConsumer(channel); + this.channels.add(channel); + this.consumers.add(consumer); + } + + } + } + } + + protected BlockingQueueConsumer createBlockingQueueConsumer(final Channel channel) throws IOException { + BlockingQueueConsumer consumer; + if (this.blockingQueueConsumerCapacity <= 0) { + consumer = new BlockingQueueConsumer(channel); + } + else { + consumer = new BlockingQueueConsumer(channel, new LinkedBlockingQueue(blockingQueueConsumerCapacity)); + } + // Set basicQos before calling basicConsume + channel.basicQos(prefetchCount); + String queueNames = getRequiredQueueName(); + + String[] queue = StringUtils.commaDelimitedListToStringArray(queueNames); + for (int i = 0; i < queue.length; i++) { + channel.queueDeclare(queue[i]); + String consumerTag = channel.basicConsume(queue[i], autoAck, consumer); + consumer.setConsumerTag(consumerTag); + } + return consumer; + } + + + protected void processMessage(Message message, Channel channel) { + boolean exposeResource = isExposeListenerChannel(); + if (exposeResource) { + TransactionSynchronizationManager.bindResource( + getConnectionFactory(), new LocallyExposedRabbitResourceHolder(channel)); + } + try { + executeListener(channel, message); + } + finally { + if (exposeResource) { + TransactionSynchronizationManager.unbindResource(getConnectionFactory()); + } + } + } + + + private class AsyncMessageProcessingConsumer implements Runnable { + + private BlockingQueueConsumer q; + private int txSize; + private long timeLimit; + private SimpleMessageListenerContainer messageListenerContainer; + + public AsyncMessageProcessingConsumer(BlockingQueueConsumer q, + int txSize, + int timeLimit, + SimpleMessageListenerContainer messageListenerContainer) { + + this.q = q; + this.txSize = txSize; + this.timeLimit = 1000L * timeLimit; + this.messageListenerContainer = messageListenerContainer; + } + + public void run() { + long now; + long startTime; + startTime = now = System.currentTimeMillis(); + int totalMsgCount = 0; + + Channel channel = q.getChannel(); + try { + for (; timeLimit == 0 || now < startTime + timeLimit; totalMsgCount++) { + Delivery delivery; + if (timeLimit == 0) { + delivery = q.nextDelivery(); + } + else { + delivery = q.nextDelivery(startTime + timeLimit - now); + if (delivery == null) break; + } + byte[] body = delivery.getBody(); + Envelope envelope = delivery.getEnvelope(); + + logger.debug("Received message from exchange [" + envelope.getExchange() + "], routing-key [" + envelope.getRoutingKey() + "]"); + + + MessageProperties messageProps = new RabbitMessageProperties(delivery.getProperties(), + envelope.getExchange(), + envelope.getRoutingKey(), + envelope.isRedeliver(), + envelope.getDeliveryTag(), + 0); + Message message = new Message(body, messageProps); + + messageListenerContainer.processMessage(message, channel); + + //TODO Should probably go to doExecuteListener() of base class. + //TODO probably can't handle batching (txSize > 1) with current implementation + //TODO Need to call rollback + if (txSize != 0 && totalMsgCount % txSize == 0) { + channel.txCommit(); + } + now = System.currentTimeMillis(); + } + //TODO should we throw here? + } + catch (IOException e) { + throw new AmqpException(e); + } + catch (InterruptedException e) { + logger.debug("Consumer thread interrupted, processing stopped."); + Thread.currentThread().interrupt(); + } + catch (ShutdownSignalException e) { + logger.debug("Consumer received ShutdownSignal, processing stopped."); + } + } + } + +} diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/adapter/ListenerExecutionFailedException.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/adapter/ListenerExecutionFailedException.java new file mode 100644 index 00000000..762f1429 --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/adapter/ListenerExecutionFailedException.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2006 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.amqp.rabbit.listener.adapter; + +import org.springframework.amqp.AmqpException; + + +/** + * Exception to be thrown when the execution of a listener method failed. + * + * @author Juergen Hoeller + * @since 2.0 + * @see MessageListenerAdapter + */ +public class ListenerExecutionFailedException extends AmqpException { + + /** + * Constructor for ListenerExecutionFailedException. + * @param msg the detail message + * @param cause the exception thrown by the listener method + */ + public ListenerExecutionFailedException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/adapter/MessageListenerAdapter.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/adapter/MessageListenerAdapter.java new file mode 100644 index 00000000..21637107 --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/listener/adapter/MessageListenerAdapter.java @@ -0,0 +1,617 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.listener.adapter; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; + +import javax.print.attribute.standard.Destination; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.amqp.AmqpException; +import org.springframework.amqp.AmqpIOException; +import org.springframework.amqp.AmqpIllegalStateException; +import org.springframework.amqp.core.Address; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageListener; +import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener; +import org.springframework.amqp.rabbit.core.RabbitMessageProperties; +import org.springframework.amqp.rabbit.support.RabbitUtils; +import org.springframework.amqp.support.converter.MessageConversionException; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.amqp.support.converter.SimpleMessageConverter; +import org.springframework.util.Assert; +import org.springframework.util.MethodInvoker; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +import com.rabbitmq.client.Channel; + +/** + * Message listener adapter that delegates the handling of messages to target + * listener methods via reflection, with flexible message type conversion. + * Allows listener methods to operate on message content types, completely + * independent from the Rabbit API. + * + *

By default, the content of incoming Rabbit messages gets extracted before + * being passed into the target listener method, to let the target method + * operate on message content types such as String or byte array instead of + * the raw {@link Message}. Message type conversion is delegated to a Spring + * AMQ {@link MessageConverter}. By default, a {@link SimpleMessageConverter} + * will be used. (If you do not want such automatic message conversion taking + * place, then be sure to set the {@link #setMessageConverter MessageConverter} + * to null.) + * + *

If a target listener method returns a non-null object (typically of a + * message content type such as String or byte array), it will get + * wrapped in a Rabbit Message and sent to the exchange of the incoming message + * with the routingKey that comes from the Rabbit ReplyTo property or via + * {@link #setDefaultResponseRoutingKey(String) specified routingKey}). + * + *

Note: The sending of response messages is only available when + * using the {@link ChannelAwareMessageListener} entry point (typically through a + * Spring message listener container). Usage as {@link MessageListener} + * does not support the generation of response messages. + * + *

Find below some examples of method signatures compliant with this + * adapter class. This first example handles all Message types + * and gets passed the contents of each Message type as an + * argument. No Message will be sent back as all of these + * methods return void. + * + *

public interface MessageContentsDelegate {
+ *    void handleMessage(String text);
+ *    void handleMessage(Map map);
+ *    void handleMessage(byte[] bytes);
+ *    void handleMessage(Serializable obj);
+ * }
+ * + * This next example handle a Message type and gets + * passed the actual (raw) Message as an argument. Again, no + * Message will be sent back as all of these methods return + * void. + * + *
public interface RawMessageDelegate {
+ *    void handleMessage(Message message);
+ * }
+ * + * This next example illustrates a Message delegate + * that just consumes the String contents of + * {@link Message Messages}. Notice also how the + * name of the Message handling method is different from the + * {@link #ORIGINAL_DEFAULT_LISTENER_METHOD original} (this will have to + * be configured in the attandant bean definition). Again, no Message + * will be sent back as the method returns void. + * + *
public interface TextMessageContentDelegate {
+ *    void onMessage(String text);
+ * }
+ * + * This final example illustrates a Message delegate + * that just consumes the String contents of + * {@link Message Messages}. Notice how the return type + * of this method is String: This will result in the configured + * {@link MessageListenerAdapter} sending a {@link Message} in response. + * + *
public interface ResponsiveTextMessageContentDelegate {
+ *    String handleMessage(String text);
+ * }
+ * + * For further examples and discussion please do refer to the Spring + * reference documentation which describes this class (and its attendant + * XML configuration) in detail. + * + * @author Juergen Hoeller + * @author Mark Pollack + * @author Mark Fisher + * @see #setDelegate + * @see #setDefaultListenerMethod + * @see #setDefaultResponseDestination + * @see #setMessageConverter + * @see org.springframework.amqp.support.converter.SimpleMessageConverter + * @see org.springframework.amqp.rabbit.core.ChannelAwareMessageListener + * @see org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer#setMessageListener + */ +public class MessageListenerAdapter implements MessageListener, ChannelAwareMessageListener { + + /** + * Out-of-the-box value for the default listener method: "handleMessage". + */ + public static final String ORIGINAL_DEFAULT_LISTENER_METHOD = "handleMessage"; + + + private static final String DEFAULT_RESPONSE_EXCHANGE = ""; + + private static final String DEFAULT_RESPONSE_ROUTING_KEY = ""; + + + // TODO configure defaults + // void basicQos(int prefetchSize, int prefetchCount, boolean global) + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + private Object delegate; + + private String defaultListenerMethod = ORIGINAL_DEFAULT_LISTENER_METHOD; + + private String defaultResponseRoutingKey = DEFAULT_RESPONSE_ROUTING_KEY; + + private String defaultRepsonseExchange = DEFAULT_RESPONSE_EXCHANGE; + + private volatile boolean mandatoryPublish; + + private volatile boolean immediatePublish; + + private MessageConverter messageConverter; + + + /** + * Create a new {@link MessageListenerAdapter} with default settings. + */ + public MessageListenerAdapter() { + initDefaultStrategies(); + this.delegate = this; + } + + /** + * Create a new {@link MessageListenerAdapter} for the given delegate. + * @param delegate the delegate object + */ + public MessageListenerAdapter(Object delegate) { + initDefaultStrategies(); + setDelegate(delegate); + } + + /** + * Create a new {@link MessageListenerAdapter} for the given delegate. + * @param delegate the delegate object + * @param messageConverter the message converter to use + */ + public MessageListenerAdapter(Object delegate, MessageConverter messageConverter) { + initDefaultStrategies(); + setDelegate(delegate); + setMessageConverter(messageConverter); + } + + + /** + * Set a target object to delegate message listening to. + * Specified listener methods have to be present on this target object. + *

If no explicit delegate object has been specified, listener + * methods are expected to present on this adapter instance, that is, + * on a custom subclass of this adapter, defining listener methods. + */ + public void setDelegate(Object delegate) { + Assert.notNull(delegate, "Delegate must not be null"); + this.delegate = delegate; + } + + /** + * Return the target object to delegate message listening to. + */ + protected Object getDelegate() { + return this.delegate; + } + + /** + * Specify the name of the default listener method to delegate to, + * for the case where no specific listener method has been determined. + * Out-of-the-box value is {@link #ORIGINAL_DEFAULT_LISTENER_METHOD "handleMessage"}. + * @see #getListenerMethodName + */ + public void setDefaultListenerMethod(String defaultListenerMethod) { + this.defaultListenerMethod = defaultListenerMethod; + } + + /** + * Return the name of the default listener method to delegate to. + */ + protected String getDefaultListenerMethod() { + return this.defaultListenerMethod; + } + + /** + * Set the default routing key to use when sending response messages. This will be applied + * in case of a request message that does not carry a "ReplyTo" property + *

Response destinations are only relevant for listener methods that return + * result objects, which will be wrapped in a response message and sent to a + * response destination. + */ + public void setDefaultResponseRoutingKey(String defaultResponseRoutingKey) { + this.defaultResponseRoutingKey = defaultResponseRoutingKey; + } + + /** + * Set the default exchange to use when sending response messages. This is only + * used if the exchange from the received message is null. + *

Response destinations are only relevant for listener methods that return + * result objects, which will be wrapped in a response message and sent to a + * response destination. + * @param defaultResponseExchange + */ + public void setDefaultResponseExchange(String defaultResponseExchange) { + this.defaultRepsonseExchange = defaultResponseExchange; + } + + /** + * Set the converter that will convert incoming Rabbit messages to + * listener method arguments, and objects returned from listener + * methods back to Rabbit messages. + *

The default converter is a {@link SimpleMessageConverter}, which is able + * to handle "text" content-types. + */ + public void setMessageConverter(MessageConverter messageConverter) { + this.messageConverter = messageConverter; + } + + /** + * Return the converter that will convert incoming Rabbit messages to + * listener method arguments, and objects returned from listener + * methods back to Rabbit messages. + */ + protected MessageConverter getMessageConverter() { + return this.messageConverter; + } + + public void setMandatoryPublish(boolean mandatoryPublish) { + this.mandatoryPublish = mandatoryPublish; + } + + public void setImmediatePublish(boolean immediatePublish) { + this.immediatePublish = immediatePublish; + } + + /** + * Rabbit {@link MessageListener} entry point. + *

Delegates the message to the target listener method, with appropriate + * conversion of the message argument. In case of an exception, the + * {@link #handleListenerException(Throwable)} method will be invoked. + *

Note: Does not support sending response messages based on + * result objects returned from listener methods. Use the + * {@link ChannelAwareMessageListener} entry point (typically through a Spring + * message listener container) for handling result objects as well. + * @param message the incoming Rabbit message + * @see #handleListenerException + * @see #onMessage(Message, Channel) + */ + public void onMessage(Message message) { + try { + onMessage(message, null); + } + catch (Throwable ex) { + handleListenerException(ex); + } + } + + /** + * Spring {@link SessionAwareMessageListener} entry point. + *

Delegates the message to the target listener method, with appropriate + * conversion of the message argument. If the target method returns a + * non-null object, wrap in a Rabbit message and send it back. + * @param message the incoming Rabbit message + * @param channel the Rabbit channel to operate on + * @throws Exception if thrown by Rabbit API methods + */ + @SuppressWarnings("unchecked") + public void onMessage(Message message, Channel channel) throws Exception { + // Check whether the delegate is a MessageListener impl itself. + // In that case, the adapter will simply act as a pass-through. + Object delegate = getDelegate(); + if (delegate != this) { + if (delegate instanceof ChannelAwareMessageListener) { + if (channel != null) { + ((ChannelAwareMessageListener) delegate).onMessage(message, channel); + return; + } + else if (!(delegate instanceof MessageListener)) { + throw new AmqpIllegalStateException("MessageListenerAdapter cannot handle a " + + "ChannelAwareMessageListener delegate if it hasn't been invoked with a Channel itself"); + } + } + if (delegate instanceof MessageListener) { + ((MessageListener) delegate).onMessage(message); + return; + } + } + + // Regular case: find a handler method reflectively. + Object convertedMessage = extractMessage(message); + String methodName = getListenerMethodName(message, convertedMessage); + if (methodName == null) { + throw new AmqpIllegalStateException("No default listener method specified: " + + "Either specify a non-null value for the 'defaultListenerMethod' property or " + + "override the 'getListenerMethodName' method."); + } + + // Invoke the handler method with appropriate arguments. + Object[] listenerArguments = buildListenerArguments(convertedMessage); + Object result = invokeListenerMethod(methodName, listenerArguments); + if (result != null) { + handleResult(result, message, channel); + } + else { + logger.trace("No result object given - no result to handle"); + } + } + + /** + * Initialize the default implementations for the adapter's strategies. + * @see #setMessageConverter + * @see org.springframework.amqp.support.converter.SimpleMessageConverter + */ + protected void initDefaultStrategies() { + setMessageConverter(new SimpleMessageConverter()); + } + + /** + * Handle the given exception that arose during listener execution. + * The default implementation logs the exception at error level. + *

This method only applies when using a Rabbit {@link MessageListener}. + * In case of the Spring {@link SessionAwareMessageListener} mechanism, + * exceptions get handled by the caller instead. + * @param ex the exception to handle + * @see #onMessage(Message) + */ + protected void handleListenerException(Throwable ex) { + logger.error("Listener execution failed", ex); + } + + /** + * Extract the message body from the given Rabbit message. + * @param message the Rabbit Message + * @return the content of the message, to be passed into the + * listener method as argument + * @throws Exception if thrown by Rabbit API methods + */ + protected Object extractMessage(Message message) throws Exception { + MessageConverter converter = getMessageConverter(); + if (converter != null) { + return converter.fromMessage(message); + } + return message; + } + + /** + * Determine the name of the listener method that is supposed to + * handle the given message. + *

The default implementation simply returns the configured + * default listener method, if any. + * @param originalMessage the Rabbit request message + * @param extractedMessage the converted Rabbit request message, + * to be passed into the listener method as argument + * @return the name of the listener method (never null) + * @throws Exception if thrown by Rabbit API methods + * @see #setDefaultListenerMethod + */ + protected String getListenerMethodName(Message originalMessage, Object extractedMessage) throws Exception { + return getDefaultListenerMethod(); + } + + /** + * Build an array of arguments to be passed into the target listener method. + * Allows for multiple method arguments to be built from a single message object. + *

The default implementation builds an array with the given message object + * as sole element. This means that the extracted message will always be passed + * into a single method argument, even if it is an array, with the target + * method having a corresponding single argument of the array's type declared. + *

This can be overridden to treat special message content such as arrays + * differently, for example passing in each element of the message array + * as distinct method argument. + * @param extractedMessage the content of the message + * @return the array of arguments to be passed into the + * listener method (each element of the array corresponding + * to a distinct method argument) + */ + protected Object[] buildListenerArguments(Object extractedMessage) { + return new Object[] {extractedMessage}; + } + + /** + * Invoke the specified listener method. + * @param methodName the name of the listener method + * @param arguments the message arguments to be passed in + * @return the result returned from the listener method + * @throws Exception if thrown by Rabbit API methods + * @see #getListenerMethodName + * @see #buildListenerArguments + */ + protected Object invokeListenerMethod(String methodName, Object[] arguments) throws Exception { + try { + MethodInvoker methodInvoker = new MethodInvoker(); + methodInvoker.setTargetObject(getDelegate()); + methodInvoker.setTargetMethod(methodName); + methodInvoker.setArguments(arguments); + methodInvoker.prepare(); + return methodInvoker.invoke(); + } + catch (InvocationTargetException ex) { + Throwable targetEx = ex.getTargetException(); + if (targetEx instanceof IOException) { + throw new AmqpIOException((IOException) targetEx); + } + else { + throw new ListenerExecutionFailedException( + "Listener method '" + methodName + "' threw exception", targetEx); + } + } + catch (Throwable ex) { + ArrayList arrayClass = new ArrayList(); + if (arguments != null) { + for (int i = 0; i < arguments.length; i++) { + arrayClass.add(arguments[i].getClass().toString()); + } + } + throw new ListenerExecutionFailedException("Failed to invoke target method '" + methodName + + "' with argument type = [" + StringUtils.collectionToCommaDelimitedString(arrayClass) + "], value = [" + + ObjectUtils.nullSafeToString(arguments) + "]", ex); + } + } + + + /** + * Handle the given result object returned from the listener method, + * sending a response message back. + * @param result the result object to handle (never null) + * @param request the original request message + * @param channel the Rabbit channel to operate on (may be null) + * @throws Exception if thrown by Rabbit API methods + * @see #buildMessage + * @see #postProcessResponse + * @see #getResponseDestination + * @see #sendResponse + */ + protected void handleResult(Object result, Message request, Channel channel) throws Exception { + if (channel != null) { + if (logger.isDebugEnabled()) { + logger.debug("Listener method returned result [" + result + + "] - generating response message for it"); + } + Message response = buildMessage(channel, result); + postProcessResponse(request, response); + String replyTo = getResponseReplyTo(request, response, channel); + String receivedExchange = getReceivedExchange(request); + sendResponse(channel, receivedExchange, replyTo, response); + } + else if (logger.isWarnEnabled()) { + logger.warn("Listener method returned result [" + result + + "]: not generating response message for it because of no Rabbit Channel given"); + } + } + + protected String getReceivedExchange(Message request) { + return request.getMessageProperties().getReceivedExchange(); + } + + /** + * Build a Rabbit message to be sent as response based on the given result object. + * @param session the Rabbit Channel to operate on + * @param result the content of the message, as returned from the listener method + * @return the Rabbit Message (never null) + * @throws Exception if thrown by Rabbit API methods + * @see #setMessageConverter + */ + protected Message buildMessage(Channel session, Object result) throws Exception { + MessageConverter converter = getMessageConverter(); + if (converter != null) { + return converter.toMessage(result, new RabbitMessageProperties()); + } + else { + if (!(result instanceof Message)) { + throw new MessageConversionException( + "No MessageConverter specified - cannot handle message [" + result + "]"); + } + return (Message) result; + } + } + + /** + * Post-process the given response message before it will be sent. + *

The default implementation sets the response's correlation id + * to the request message's correlation id, if any; otherwise to the + * request message id. + * @param request the original incoming Rabbit message + * @param response the outgoing Rabbit message about to be sent + * @throws Exception if thrown by Rabbit API methods + */ + protected void postProcessResponse(Message request, Message response) throws Exception { + byte[] correlation = request.getMessageProperties().getCorrelationId(); + + if (correlation == null) { + correlation = request.getMessageProperties().getMessageId().getBytes(SimpleMessageConverter.DEFAULT_CHARSET); + } + response.getMessageProperties().setCorrelationId(correlation); + } + + /** + * Determine a response destination for the given message. + *

The default implementation first checks the Rabbit Reply-To + * Queue of the supplied request; if that is not null + * it is returned; if it is null, then the configured + * {@link #resolveDefaultResponseQueue default response destination} + * is returned; if this too is null, then an + * {@link InvalidDestinationException} is thrown. + * @param request the original incoming Rabbit message + * @param response the outgoing Rabbit message about to be sent + * @param channel the Rabbit Channel to operate on + * @return the response destination (never null) + * @throws Exception if thrown by Rabbit API methods + * @throws InvalidDestinationException if no {@link Destination} can be determined + * @see #setDefaultResponseDestination + * @see org.springframework.amqp.core.Message.getMessageProperties().getReplyTo(); + */ + protected String getResponseReplyTo(Message request, Message response, Channel channel) + throws Exception { + + Address replyTo = request.getMessageProperties().getReplyTo(); + if (replyTo == null) { + replyTo = new Address(defaultResponseRoutingKey); + if (replyTo == null) { + throw new AmqpException("Cannot determine ReplyTo message property value: " + + "Request message does not contain reply-to property, and no default ReplyTo value set."); + } + } + return replyTo.toString(); + } + + + /** + * Send the given response message to the given destination. + * @param channel the Rabbit channel to operate on + * @param receivedExchange the exchange on which the incoming message was received.b + * @param replyTo the Rabbit ReplyTo string to use when sending. Currently interpreted to be the routing key. + * @param message the Rabbit message to send + * @throws Exception if thrown by Rabbit API methods + * @see #postProcessProducer + */ + protected void sendResponse(Channel channel, String receivedExchange, String replyTo, Message message) throws Exception { + + //TODO this needs to be improved to have a strategy for extracting exchange/routingKey from replyTo and incorporating a defaultReplyToExchange + String exchange = this.defaultRepsonseExchange; + if (receivedExchange != null) { + exchange = receivedExchange; + logger.debug("Overriding defaultResponseExchange and using exchange of received message."); + } + String routingKey = replyTo; + postProcessChannel(channel, message); + + //TODO parameterize out default encoding, How to ensure there is a valid binding for this publish? + try { + logger.debug("Publishing response to exchanage = [" + exchange + "], routingKey = [" + routingKey + "]"); + channel.basicPublish(exchange, routingKey, + this.mandatoryPublish, this.immediatePublish, + RabbitUtils.extractBasicProperties(message, "UTF-8"), + message.getBody()); + } catch (Exception ex) { + throw RabbitUtils.convertRabbitAccessException(ex); + } + } + + /** + * Post-process the given message producer before using it to send the response. + *

The default implementation is empty. + * @param producer the Rabbit Channel that will be used to send the message + * @param response the outgoing Rabbit message about to be sent + * @throws Exception if thrown by Rabbit API methods + */ + protected void postProcessChannel(Channel channel, Message response) throws Exception { + } + +} diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/RabbitAccessor.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/RabbitAccessor.java new file mode 100644 index 00000000..3adfef1e --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/RabbitAccessor.java @@ -0,0 +1,110 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.support; + +import java.io.IOException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.amqp.AmqpException; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; + +/** + * @author Mark Fisher + */ +public abstract class RabbitAccessor implements InitializingBean { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + private volatile ConnectionFactory connectionFactory; + + private volatile boolean channelTransacted; + + + /** + * Set the ConnectionFactory to use for obtaining RabbitMQ {@link Connection Connections}. + */ + public void setConnectionFactory(ConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + } + + /** + * Return the ConnectionFactory that this accessor uses for obtaining + * RabbitMQ {@link Connection Connections}. + */ + public ConnectionFactory getConnectionFactory() { + return this.connectionFactory; + } + + /** + * Set the transaction mode that is used for a RabbitMQ {@link Channel}, + * Default is "false". + */ + public void setChannelTransacted(boolean channelTransacted) { + this.channelTransacted = channelTransacted; + } + + /** + * Return whether the RabbitMQ {@link Channel channels} used by this + * accessor are supposed to be transacted. + * @see #setChannelTransacted(boolean) + */ + public boolean isChannelTransacted() { + return this.channelTransacted; + } + + public void afterPropertiesSet() { + Assert.notNull(getConnectionFactory(), "ConnectionFactory is required"); + } + + /** + * Create a RabbitMQ Connection via this template's ConnectionFactory + * and its host and port values. + * @return the new RabbitMQ Connection + * @throws IOException if thrown by RabbitMQ API methods + * @see ConnectionFactory#newConnection(String, int) + */ + protected Connection createConnection() throws IOException { + return getConnectionFactory().createConnection(); + } + + /** + * Create a RabbitMQ Channel for the given Connection. + * @param con the RabbitMQ Connection to create a Channel for + * @return the new RabbitMQ Channel + * @throws IOException if thrown by RabbitMQ API methods + */ + protected Channel createChannel(Connection con) throws IOException { + Assert.notNull(con, "connection must not be null"); + Channel channel = con.createChannel(); + if (isChannelTransacted()) { + channel.txSelect(); + } + return channel; + } + + protected AmqpException convertRabbitAccessException(Exception ex) { + return RabbitUtils.convertRabbitAccessException(ex); + } + +} diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/RabbitUtils.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/RabbitUtils.java new file mode 100644 index 00000000..96121293 --- /dev/null +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/RabbitUtils.java @@ -0,0 +1,164 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.support; + +import java.beans.PropertyDescriptor; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Method; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.amqp.AmqpException; +import org.springframework.amqp.AmqpIOException; +import org.springframework.amqp.AmqpUnsupportedEncodingException; +import org.springframework.amqp.UncategorizedAmqpException; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.amqp.rabbit.core.RabbitMessageProperties; +import org.springframework.beans.BeanUtils; +import org.springframework.util.Assert; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; + +/** + * @author Mark Fisher + * @author Mark Pollack + */ +public abstract class RabbitUtils { + + private static final Log logger = LogFactory.getLog(RabbitUtils.class); + + + public static final int DEFAULT_PORT = AMQP.PROTOCOL.PORT; + + + /** + * Close the given RabbitMQ Connection and ignore any thrown exception. + * This is useful for typical finally blocks in manual RabbitMQ code. + * @param connection the RabbitMQ Connection to close (may be null) + */ + public static void closeConnection(Connection connection) { + if (connection != null) { + try { + connection.close(); + } + catch (Exception ex) { + logger.debug("Ignoring Connection exception - assuming already closed: " + ex); + } + } + } + + /** + * Close the given RabbitMQ Channel and ignore any thrown exception. + * This is useful for typical finally blocks in manual RabbitMQ code. + * @param channel the RabbitMQ Channel to close (may be null) + */ + public static void closeChannel(Channel channel) { + if (channel != null) { + try { + channel.close(); + } + catch (IOException ex) { + logger.debug("Could not close RabbitMQ Channel", ex); + } + catch (Throwable ex) { + logger.debug("Unexpected exception on closing RabbitMQ Channel", ex); + } + } + } + + /** + * Commit the Channel if not within a JTA transaction. + * @param channel the RabbitMQ Channel to commit + * @throws IOException + * @throws IOException if committing failed + */ + public static void commitIfNecessary(Channel channel) { + Assert.notNull(channel, "Channel must not be null"); + try { + channel.txCommit(); + } + catch (IOException ex) { + logger.error("Could not commit RabbitMQ Channel", ex); + } + } + + public static void rollbackIfNecessary(Channel channel) { + Assert.notNull(channel, "Channel must not be null"); + try { + channel.txRollback(); + } + catch (IOException ex) { + logger.error("Could not rollback RabbitMQ Channel", ex); + } + } + + public static AmqpException convertRabbitAccessException(Exception ex) { + Assert.notNull(ex, "Exception must not be null"); + if (ex instanceof IOException) { + return new AmqpIOException((IOException) ex); + } + if (ex instanceof UnsupportedEncodingException) { + return new AmqpUnsupportedEncodingException((UnsupportedEncodingException) ex); + } + //fallback + return new UncategorizedAmqpException(ex); + } + + public static void closeMessageConsumer(Channel channel, String consumerTag) { + try { + channel.basicCancel(consumerTag); + } + catch (Exception ex) { + convertRabbitAccessException(ex); + } + } + + public static com.rabbitmq.client.AMQP.BasicProperties extractBasicProperties(Message message, String encoding) { + MessageProperties sourceProperties = message.getMessageProperties(); + if (sourceProperties instanceof RabbitMessageProperties) { + return ((RabbitMessageProperties) sourceProperties).getBasicProperties(); + } + RabbitMessageProperties targetProperties = new RabbitMessageProperties(); + PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(MessageProperties.class); + for (PropertyDescriptor descriptor : descriptors) { + Method getter = descriptor.getReadMethod(); + Method setter = descriptor.getWriteMethod(); + if (getter != null || setter != null) { + try { + Object value = getter.invoke(sourceProperties, new Object[0]); + if (value != null) { + setter.invoke(targetProperties, value); + } + } + catch (Exception e) { + // ignore + } + } + } + for (Map.Entry entry : sourceProperties.getHeaders().entrySet()) { + targetProperties.setHeader(entry.getKey(), entry.getValue()); + } + return targetProperties.getBasicProperties(); + } + +} diff --git a/spring-rabbit/src/main/resources/META-INF/spring.handlers b/spring-rabbit/src/main/resources/META-INF/spring.handlers new file mode 100644 index 00000000..c54d5bd5 --- /dev/null +++ b/spring-rabbit/src/main/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +http\://www.springframework.org/schema/rabbitmq=org.springframework.amqp.rabbit.config.RabbitNamespaceHandler diff --git a/spring-rabbit/src/main/resources/META-INF/spring.schemas b/spring-rabbit/src/main/resources/META-INF/spring.schemas new file mode 100644 index 00000000..5a47fc39 --- /dev/null +++ b/spring-rabbit/src/main/resources/META-INF/spring.schemas @@ -0,0 +1,2 @@ +http\://www.springframework.org/schema/rabbitmq/spring-rabbit-1.0.xsd=org/springframework/amqp/rabbit/config/spring-rabbit-1.0.xsd +http\://www.springframework.org/schema/rabbitmq/spring-rabbit.xsd=org/springframework/amqp/rabbit/config/spring-rabbit-1.0.xsd diff --git a/spring-rabbit/src/main/resources/META-INF/spring.tooling b/spring-rabbit/src/main/resources/META-INF/spring.tooling new file mode 100644 index 00000000..98455f1c --- /dev/null +++ b/spring-rabbit/src/main/resources/META-INF/spring.tooling @@ -0,0 +1,4 @@ +# Tooling related information for the jms namespace +http\://www.springframework.org/schema/rabbit@name=rabbit Namespace +http\://www.springframework.org/schema/rabbit@prefix=rabbit +http\://www.springframework.org/schema/rabbit@icon=org/springframework/amqp/rabbit/config/spring-rabbit.gif diff --git a/spring-rabbit/src/main/resources/org/springframework/amqp/rabbit/config/spring-rabbit-1.0.xsd b/spring-rabbit/src/main/resources/org/springframework/amqp/rabbit/config/spring-rabbit-1.0.xsd new file mode 100644 index 00000000..dc8c7f72 --- /dev/null +++ b/spring-rabbit/src/main/resources/org/springframework/amqp/rabbit/config/spring-rabbit-1.0.xsd @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-rabbit/src/main/resources/org/springframework/amqp/rabbit/config/spring-rabbit.gif b/spring-rabbit/src/main/resources/org/springframework/amqp/rabbit/config/spring-rabbit.gif new file mode 100644 index 00000000..731b7a90 Binary files /dev/null and b/spring-rabbit/src/main/resources/org/springframework/amqp/rabbit/config/spring-rabbit.gif differ diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/config/RabbitAdminNamespaceHandlerTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/config/RabbitAdminNamespaceHandlerTests.java new file mode 100644 index 00000000..497ba996 --- /dev/null +++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/config/RabbitAdminNamespaceHandlerTests.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2008 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.amqp.rabbit.config; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Map; + +import org.junit.Test; +import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.core.io.Resource; + +/** + * + */ +public final class RabbitAdminNamespaceHandlerTests { + + + @Test + public void testParse() throws Exception { + ApplicationContext applicationContext = new ClassPathXmlApplicationContext( + "rabbitAdminNamespaceHandlerTests.xml", getClass()); + + /* + * Map beans = applicationContext + .getBeansOfType(PropertyPlaceholderConfigurer.class); + assertFalse("No PropertyPlaceHolderConfigurer found", beans.isEmpty()); + String s = (String) applicationContext.getBean("string"); + assertEquals("No properties replaced", "bar", s); + */ + } + +} diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/CachingConnectionFactoryTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/CachingConnectionFactoryTests.java new file mode 100644 index 00000000..df037cf1 --- /dev/null +++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/CachingConnectionFactoryTests.java @@ -0,0 +1,144 @@ +package org.springframework.amqp.rabbit.connection; + +import static org.mockito.Mockito.*; + +import java.io.IOException; + +import junit.framework.Assert; + +import org.junit.Test; +import org.springframework.amqp.rabbit.support.RabbitUtils; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; + +/** + * @author Mark Pollack + */ +public class CachingConnectionFactoryTests { + + @Test + public void testWithConnectionFactoryDefaults() throws IOException { + com.rabbitmq.client.ConnectionFactory mockConnectionFactory = mock(com.rabbitmq.client.ConnectionFactory.class); + Connection mockConnection = mock(Connection.class); + Channel mockChannel = mock(Channel.class); + + when(mockConnectionFactory.newConnection("localhost", RabbitUtils.DEFAULT_PORT)).thenReturn(mockConnection); + when(mockConnection.createChannel()).thenReturn(mockChannel); + + CachingConnectionFactory ccf = new CachingConnectionFactory(mockConnectionFactory, "localhost"); + ccf.afterPropertiesSet(); + Connection con = ccf.createConnection(); + + Channel channel = con.createChannel(); + channel.close(); // should be ignored, and placed into channel cache. + con.close(); // should be ignored + + Connection con2 = ccf.createConnection(); + Channel channel2 = con2.createChannel(); // will retrieve same channel object that was just put into channel cache + channel2.close(); // should be ignored + con2.close(); // should be ignored + + Assert.assertSame(con, con2); + Assert.assertSame(channel, channel2); + verify(mockConnection, never()).close(); + verify(mockChannel, never()).close(); + + } + + @Test + public void testWithConnectionFactoryCacheSize() throws IOException { + com.rabbitmq.client.ConnectionFactory mockConnectionFactory = mock(com.rabbitmq.client.ConnectionFactory.class); + Connection mockConnection = mock(Connection.class); + Channel mockChannel1 = mock(Channel.class); + Channel mockChannel2 = mock(Channel.class); + + when(mockConnectionFactory.newConnection("localhost", RabbitUtils.DEFAULT_PORT)).thenReturn(mockConnection); + when(mockConnection.createChannel()).thenReturn(mockChannel1); + when(mockConnection.createChannel()).thenReturn(mockChannel2); + + CachingConnectionFactory ccf = new CachingConnectionFactory(mockConnectionFactory, "localhost"); + ccf.setChannelCacheSize(2); + ccf.afterPropertiesSet(); + + Connection con = ccf.createConnection(); + + Channel channel1 = con.createChannel(); + Channel channel2 = con.createChannel(); + + channel1.close(); // should be ignored, and add last into channel cache. + channel2.close(); // should be ignored, and add last into channel cache. + + Channel ch1 = con.createChannel(); // remove first entry in cache (channel1) + Channel ch2 = con.createChannel(); // remove first entry in cache (channel2) + + Assert.assertNotSame(ch1, ch2); + Assert.assertSame(ch1, channel1); + Assert.assertSame(ch2, channel2); + + ch1.close(); + ch2.close(); + + verify(mockConnection, times(2)).createChannel(); + + con.close(); // should be ignored + + verify(mockConnection, never()).close(); + verify(mockChannel1, never()).close(); + verify(mockChannel2, never()).close(); + + } + + @Test + public void testWithConnectionFactoryDestory() throws IOException { + com.rabbitmq.client.ConnectionFactory mockConnectionFactory = mock(com.rabbitmq.client.ConnectionFactory.class); + Connection mockConnection = mock(Connection.class); + + Channel mockChannel1 = mock(Channel.class); + Channel mockChannel2 = mock(Channel.class); + + Assert.assertNotSame(mockChannel1, mockChannel2); + + when(mockConnectionFactory.newConnection("localhost", RabbitUtils.DEFAULT_PORT)).thenReturn(mockConnection); + //You can't repeat 'when' statements for stubbing consecutive calls to the same method to returning different values. + stub(mockConnection.createChannel()).toReturn(mockChannel1).toReturn(mockChannel2); + + CachingConnectionFactory ccf = new CachingConnectionFactory(mockConnectionFactory, "localhost"); + ccf.setChannelCacheSize(2); + ccf.afterPropertiesSet(); + + Connection con = ccf.createConnection(); + + Channel channel1 = con.createChannel(); // This will return a Spring AOP proxy that surpresses calls to close + Channel channel2 = con.createChannel(); // " + + channel1.close(); // should be ignored, and add last into channel cache. + channel2.close(); // " + + Channel ch1 = con.createChannel(); // remove first entry in cache (channel1) + Channel ch2 = con.createChannel(); // remove first entry in cache (channel2) + + + Assert.assertSame(ch1, channel1); + Assert.assertSame(ch2, channel2); + + Channel target1 = ((ChannelProxy)ch1).getTargetChannel(); + Channel target2 = ((ChannelProxy)ch2).getTargetChannel(); + + Assert.assertNotSame(target1, target2); // make sure mokito returned different mocks for the channel + + ch1.close(); + ch2.close(); + con.close(); // should be ignored + + ccf.destroy(); // should call close on connection and channels in cache + + verify(mockConnection, times(2)).createChannel(); + + verify(mockConnection, times(1)).close(); + + //verify(mockChannel1).close(); + verify(mockChannel2, times(1)).close(); + + } +} diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/Constants.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/Constants.java new file mode 100644 index 00000000..7e06b9bd --- /dev/null +++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/Constants.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2010 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.amqp.rabbit.core; + +/** + * Exchange, queue, and routing key constants for the testing code. + * + */ +public class Constants { + + public static String EXCHANGE_NAME = ""; + + public static String QUEUE_NAME = "foo"; + + public static String ROUTING_KEY = "foo"; + + public static int NUM_MESSAGES = 500; + +} diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/PojoHandler.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/PojoHandler.java new file mode 100644 index 00000000..77641ea5 --- /dev/null +++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/PojoHandler.java @@ -0,0 +1,15 @@ +package org.springframework.amqp.rabbit.core; + +import java.util.concurrent.atomic.AtomicInteger; + +public class PojoHandler { + + private final AtomicInteger messageCount = new AtomicInteger(); + + + public void handleMessage(String textMessage) { + int msgCount = this.messageCount.incrementAndGet(); + System.out.println("Thread [" + Thread.currentThread().getId() + "] PojoHandler Received Message " + msgCount + ", = " + textMessage); + + } +} diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/Producer.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/Producer.java new file mode 100644 index 00000000..b9e5d8c0 --- /dev/null +++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/Producer.java @@ -0,0 +1,49 @@ +package org.springframework.amqp.rabbit.core; + +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageCreator; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.amqp.core.TestMessageProperties; +import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; +//import org.springframework.otp.erlang.connection.ConnectionParameters; + + +public class Producer { + + /** + * @param args + */ + public static void main(String[] args) { + CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost"); + connectionFactory.setUsername("guest"); + connectionFactory.setPassword("guest"); + connectionFactory.setChannelCacheSize(10); + + RabbitTemplate template = new RabbitTemplate(); + template.setConnectionFactory(connectionFactory); + template.setChannelTransacted(true); + template.afterPropertiesSet(); + + final String routingKey = Constants.ROUTING_KEY; + QueueUtils.declareTestQueue(template, routingKey); + + // send message + sendMessages(template, Constants.EXCHANGE_NAME, routingKey, Constants.NUM_MESSAGES); + } + + private static void sendMessages(RabbitTemplate template, + final String exchange, final String routingKey, int numMessages) { + for (int i = 1; i <= numMessages; i++) { + template.send(exchange, routingKey, new MessageCreator() { + public Message createMessage() { + byte[] bytes = "testing".getBytes(); + MessageProperties properties = new TestMessageProperties(); + properties.getHeaders().put("float", new Float(3.14)); + //properties.getHeaders().put("object", new ConnectionParameters(null,null)); + return new Message(bytes, properties); + } + }); + System.out.println("sending " + i + "..."); + } + } +} diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/QueueUtils.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/QueueUtils.java new file mode 100644 index 00000000..7a78f69a --- /dev/null +++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/QueueUtils.java @@ -0,0 +1,23 @@ +package org.springframework.amqp.rabbit.core; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.AMQP.Queue; +import com.rabbitmq.client.AMQP.Queue.DeclareOk; + +public class QueueUtils { + + static void declareTestQueue(RabbitTemplate template, + final String routingKey) { + // declare and bind queue + template.execute(new ChannelCallback() { + public String doInRabbit(Channel channel) throws Exception { + Queue.DeclareOk res = channel.queueDeclare(Constants.QUEUE_NAME); + String queueName = res.getQueue(); + System.out.println("Queue Name = " + queueName); + channel.queueBind(queueName, Constants.EXCHANGE_NAME, routingKey); + return queueName; + } + }); + } + +} diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/support/RabbitGatewaySupportTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/support/RabbitGatewaySupportTests.java new file mode 100644 index 00000000..259d573e --- /dev/null +++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/support/RabbitGatewaySupportTests.java @@ -0,0 +1,46 @@ +package org.springframework.amqp.rabbit.core.support; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; +import org.springframework.amqp.rabbit.core.RabbitTemplate; + + +public class RabbitGatewaySupportTests { + + @Test + public void testRabbitGatewaySupportWithConnectionFactory() throws Exception { + + org.springframework.amqp.rabbit.connection.ConnectionFactory mockConnectionFactory = mock(org.springframework.amqp.rabbit.connection.ConnectionFactory.class); + final List test = new ArrayList(); + RabbitGatewaySupport gateway = new RabbitGatewaySupport() { + protected void initGateway() { + test.add("test"); + } + }; + gateway.setConnectionFactory(mockConnectionFactory); + gateway.afterPropertiesSet(); + assertEquals("Correct ConnectionFactory", mockConnectionFactory, gateway.getConnectionFactory()); + assertEquals("Correct RabbitTemplate", mockConnectionFactory, gateway.getRabbitTemplate().getConnectionFactory()); + assertEquals("initGatway called", test.size(), 1); + } + + @Test + public void testRabbitGatewaySupportWithJmsTemplate() throws Exception { + RabbitTemplate template = new RabbitTemplate(); + final List test = new ArrayList(); + RabbitGatewaySupport gateway = new RabbitGatewaySupport() { + protected void initGateway() { + test.add("test"); + } + }; + gateway.setRabbitTemplate(template); + gateway.afterPropertiesSet(); + assertEquals("Correct RabbitTemplate", template, gateway.getRabbitTemplate()); + assertEquals("initGateway called", test.size(), 1); + } +} diff --git a/spring-rabbit/src/test/resources/log4j.properties b/spring-rabbit/src/test/resources/log4j.properties new file mode 100644 index 00000000..bff79664 --- /dev/null +++ b/spring-rabbit/src/test/resources/log4j.properties @@ -0,0 +1,9 @@ +log4j.rootCategory=INFO, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n + +log4j.category.org.springframework.amqp.rabbit=DEBUG +log4j.category.org.springframework.beans.factory=INFO + diff --git a/spring-rabbit/src/test/resources/org/springframework/amqp/rabbit/config/rabbitAdminNamespaceHandlerTests.xml b/spring-rabbit/src/test/resources/org/springframework/amqp/rabbit/config/rabbitAdminNamespaceHandlerTests.xml new file mode 100644 index 00000000..e8ebfeea --- /dev/null +++ b/spring-rabbit/src/test/resources/org/springframework/amqp/rabbit/config/rabbitAdminNamespaceHandlerTests.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/spring-rabbit/template.mf b/spring-rabbit/template.mf new file mode 100644 index 00000000..fae4d07e --- /dev/null +++ b/spring-rabbit/template.mf @@ -0,0 +1,11 @@ +Bundle-SymbolicName: org.springframework.amqp.rabbit +Bundle-Name: Spring RabbitMQ Support +Bundle-Vendor: SpringSource +Bundle-ManifestVersion: 2 +Import-Template: + org.springframework.*;version="[3.0.3, 4.0.0)", + org.apache.commons.logging;version="[1.1.1, 2.0.0)", + com.rabbitmq.*;version="[1.7.2, 2.0.0)", + org.junit.*;version="0", + org.w3c.dom.*;version="0", + javax.xml.*;version="0" diff --git a/src/assembly/distribution.xml b/src/assembly/distribution.xml new file mode 100644 index 00000000..ea675d36 --- /dev/null +++ b/src/assembly/distribution.xml @@ -0,0 +1,82 @@ + + + + distribution + + zip + + true + + + + src/main/resources + + readme.txt + apache-license.txt + epl-license.txt + notice.txt + changelog.txt + + + dos + + + + target/site/reference + docs/reference + + + + target/site/apidocs + docs/javadoc + + + + spring-amqp-samples + + */target/ + + samples + + + + + + + org.springframework.amqp:spring-amqp-core + org.springframework.amqp:spring-otp + org.springframework.amqp:spring-rabbit + org.springframework.amqp:spring-rabbit-admin + + + dist + false + false + + + + + + org.springframework.amqp:spring-amqp-core + org.springframework.amqp:spring-otp + org.springframework.amqp:spring-rabbit + org.springframework.amqp:spring-rabbit-admin + + + sources + src + false + false + + + + diff --git a/src/docbkx/amqp.xml b/src/docbkx/amqp.xml new file mode 100644 index 00000000..973417f0 --- /dev/null +++ b/src/docbkx/amqp.xml @@ -0,0 +1,72 @@ + + + + Using Spring AMQP + + In this chapter, we will explore interface and classes that are the + essential components for developing applications with Spring AMQP. + + + AMQP Abstractions + + Discuss classes in the org.springframework.amqp.core package such as + Queue, Binding, Message MessageProperties etc. + + + + Connection and resource management + + Discuss CachingConnectionFactory and future support for failove of + connections. + + + + Sending messages + + Use of RabbitTemplate to send a message + + + + Receiving messages + + Use of RabbitTemplate for sync recieve and MessageListenerContiner + for async + + + Synchronous Reception + + RabbitTemplate + + + + Asynchronous Reception + + Simple Message Listner container + + + + + Message Converters + + Simple and JSON + + + + Configuring the broker + + AbstractRabbitConfiguraiton BindingBuilder... + + + + Exception Handling + + Runtime exception hierarchy. + + + + JMX Monitoring + + RabbitAdminTemplate + + diff --git a/src/docbkx/bibliography.xml b/src/docbkx/bibliography.xml new file mode 100644 index 00000000..c8baa1fd --- /dev/null +++ b/src/docbkx/bibliography.xml @@ -0,0 +1,24 @@ + + + + Bibliography + + + + + Ericsson AB + + + + <ulink + url="http://www.erlang.org/doc/apps/jinterface/jinterface.pdf">jinterface + User Guide</ulink> + + + Ericson AB + + + 2000 + + diff --git a/src/docbkx/erlang.xml b/src/docbkx/erlang.xml new file mode 100644 index 00000000..5798405d --- /dev/null +++ b/src/docbkx/erlang.xml @@ -0,0 +1,44 @@ + + + + Erlang integration + +

+ Introduction + + JInterface ease of use issues. +
+ +
+ Communicating with Erlang processes + + Introduction.... + +
+ Connection Management + + ConnectionFactory +
+ +
+ Executing RPC + + +
+ +
+ ErlangConverter + + ErlangConverter +
+
+ +
+ Exceptions + + Runtime exception hierarchy + + +
+ diff --git a/src/docbkx/index.xml b/src/docbkx/index.xml new file mode 100644 index 00000000..67fae1fa --- /dev/null +++ b/src/docbkx/index.xml @@ -0,0 +1,85 @@ + + + + + Spring AMQP - Reference Documentation + &version; + + + + Mark + Pollack + + + Mark + Fisher + + + Oleg + Zhurakousky + + + + + + Copies of this document may be made for your own use and for distribution + to others, provided that you do not charge any fee for such copies and + further provided that each copy contains this Copyright Notice, whether + distributed in print or electronically. + + + + + + + + + Introduction + + + This first part of the reference documentation + is an overview of + Spring AMQP and the underlying concepts. + + + + + + Reference + + + This part of the reference documentation details the various components + that comprise Spring AMQP. The main chapter + covers the core classes to develop an AMQP application. It part also + includes a chapter on integration with + Erlang and a chapter + about the sample applications. + + + + + + + + Spring Integration - Reference + + + This part of the reference documentation details the + integration with the Spring Integration project. + + + + + + Other Resources + + + In addition to this reference documentation, there exist a number of + other resources that may help you learn about AMQP. + These additional, third-party resources are enumerated in this section. + + + + + diff --git a/src/docbkx/preface.xml b/src/docbkx/preface.xml new file mode 100644 index 00000000..46263ad1 --- /dev/null +++ b/src/docbkx/preface.xml @@ -0,0 +1,8 @@ + + + + Preface + + Overivew of AMQP and goals of Spring AMQP project. + diff --git a/src/docbkx/quick-tour.xml b/src/docbkx/quick-tour.xml new file mode 100644 index 00000000..bb437867 --- /dev/null +++ b/src/docbkx/quick-tour.xml @@ -0,0 +1,12 @@ + + + + Quick Tour for the impatient + +
+ Introduction + + This is the 5 minute tour to get started with Spring AMQP. +
+
diff --git a/src/docbkx/resources/css/highlight.css b/src/docbkx/resources/css/highlight.css new file mode 100644 index 00000000..ffefef72 --- /dev/null +++ b/src/docbkx/resources/css/highlight.css @@ -0,0 +1,35 @@ +/* + code highlight CSS resemblign the Eclipse IDE default color schema + @author Costin Leau +*/ + +.hl-keyword { + color: #7F0055; + font-weight: bold; +} + +.hl-comment { + color: #3F5F5F; + font-style: italic; +} + +.hl-multiline-comment { + color: #3F5FBF; + font-style: italic; +} + +.hl-tag { + color: #3F7F7F; +} + +.hl-attribute { + color: #7F007F; +} + +.hl-value { + color: #2A00FF; +} + +.hl-string { + color: #2A00FF; +} \ No newline at end of file diff --git a/src/docbkx/resources/css/html.css b/src/docbkx/resources/css/html.css new file mode 100644 index 00000000..10936f33 --- /dev/null +++ b/src/docbkx/resources/css/html.css @@ -0,0 +1,421 @@ +body { + text-align: justify; + margin-right: 2em; + margin-left: 2em; +} + +a, + a[accesskey^ + += +"h" +] +, +a[accesskey^ + += +"n" +] +, +a[accesskey^ + += +"u" +] +, +a[accesskey^ + += +"p" +] +{ +font-family: Verdana, Arial, helvetica, sans-serif + +; +font-size: + +12 +px + +; +color: #003399 + +; +} + +a:active { + color: #003399; +} + +a:visited { + color: #888888; +} + +p { + font-family: Verdana, Arial, sans-serif; +} + +dt { + font-family: Verdana, Arial, sans-serif; + font-size: 12px; +} + +p, dl, dt, dd, blockquote { + color: #000000; + margin-bottom: 3px; + margin-top: 3px; + padding-top: 0px; +} + +ol, ul, p { + margin-top: 6px; + margin-bottom: 6px; +} + +p, blockquote { + font-size: 90%; +} + +p.releaseinfo { + font-size: 100%; + font-weight: bold; + font-family: Verdana, Arial, helvetica, sans-serif; + padding-top: 10px; +} + +p.pubdate { + font-size: 120%; + font-weight: bold; + font-family: Verdana, Arial, helvetica, sans-serif; +} + +td { + font-size: 80%; +} + +td, th, span { + color: #000000; +} + +td[width^ + += +"40%" +] +{ +font-family: Verdana, Arial, helvetica, sans-serif + +; +font-size: + +12 +px + +; +color: #003399 + +; +} + +table[summary^ + += +"Navigation header" +] +tbody tr th[colspan^ + += +"3" +] +{ +font-family: Verdana, Arial, helvetica, sans-serif + +; +} + +blockquote { + margin-right: 0px; +} + +h1, h2, h3, h4, h6, H6 { + color: #000000; + font-weight: 500; + margin-top: 0px; + padding-top: 14px; + font-family: Verdana, Arial, helvetica, sans-serif; + margin-bottom: 0px; +} + +h2.title { + font-weight: 800; + margin-bottom: 8px; +} + +h2.subtitle { + font-weight: 800; + margin-bottom: 20px; +} + +.firstname, .surname { + font-size: 12px; + font-family: Verdana, Arial, helvetica, sans-serif; +} + +table { + border-collapse: collapse; + border-spacing: 0; + border: 1px black; + empty-cells: hide; + margin: 10px 0px 30px 50px; + width: 90%; +} + +div.table { + margin: 30px 0px 30px 0px; + border: 1px dashed gray; + padding: 10px; +} + +div .table-contents table { + border: 1px solid black; +} + +div.table > p.title { + padding-left: 10px; +} + +table[summary^ + += +"Navigation footer" +] +{ +border-collapse: collapse + +; +border-spacing: + +0 +; +border: + +1 +px black + +; +empty-cells: hide + +; +margin: + +0 +px + +; +width: + +100 +% +; +} + +table[summary^ + += +"Note" +] +, +table[summary^ + += +"Warning" +] +, +table[summary^ + += +"Tip" +] +{ +border-collapse: collapse + +; +border-spacing: + +0 +; +border: + +1 +px black + +; +empty-cells: hide + +; +margin: + +10 +px + +0 +px + +10 +px + +- +20 +px + +; +width: + +100 +% +; +} + +td { + padding: 4pt; + font-family: Verdana, Arial, helvetica, sans-serif; +} + +div.warning TD { + text-align: justify; +} + +h1 { + font-size: 150%; +} + +h2 { + font-size: 110%; +} + +h3 { + font-size: 100%; + font-weight: bold; +} + +h4 { + font-size: 90%; + font-weight: bold; +} + +h5 { + font-size: 90%; + font-style: italic; +} + +h6 { + font-size: 100%; + font-style: italic; +} + +tt { + font-size: 110%; + font-family: "Courier New", Courier, monospace; + color: #000000; +} + +.navheader, .navfooter { + border: none; +} + +div.navfooter table { + border: dashed gray; + border-width: 1px 1px 1px 1px; + background-color: #cde48d; +} + +pre { + font-size: 110%; + padding: 5px; + border-style: solid; + border-width: 1px; + border-color: #CCCCCC; + background-color: #f3f5e9; +} + +ul, ol, li { + list-style: disc; +} + +hr { + width: 100%; + height: 1px; + background-color: #CCCCCC; + border-width: 0px; + padding: 0px; +} + +.variablelist { + padding-top: 10px; + padding-bottom: 10px; + margin: 0; +} + +.term { + font-weight: bold; +} + +.mediaobject { + padding-top: 30px; + padding-bottom: 30px; +} + +.legalnotice { + font-family: Verdana, Arial, helvetica, sans-serif; + font-size: 12px; + font-style: italic; +} + +.sidebar { + float: right; + margin: 10px 0px 10px 30px; + padding: 10px 20px 20px 20px; + width: 33%; + border: 1px solid black; + background-color: #F4F4F4; + font-size: 14px; +} + +.property { + font-family: "Courier New", Courier, monospace; +} + +a code { + font-family: Verdana, Arial, monospace; + font-size: 12px; +} + +td code { + font-size: 110%; +} + +div.note * td, + div.tip * td, + div.warning * td, + div.calloutlist * td { + text-align: justify; + font-size: 100%; +} + +.programlisting .interfacename, + .programlisting .literal, + .programlisting .classname { + font-size: 95%; +} + +.title .interfacename, + .title .literal, + .title .classname { + font-size: 130%; +} + +/* everything in a is displayed in a coloured, comment-like font */ +.programlisting * .lineannotation, + .programlisting * .lineannotation * { + color: green; +} diff --git a/src/docbkx/resources/css/stylesheet.css b/src/docbkx/resources/css/stylesheet.css new file mode 100644 index 00000000..77569070 --- /dev/null +++ b/src/docbkx/resources/css/stylesheet.css @@ -0,0 +1,99 @@ +@IMPORT url("highlight.css"); + +html { + padding: 0pt; + margin: 0pt; +} + +body { + margin-left: 10%; + margin-right: 10%; + font-family: Arial, Sans-serif; +} + +div { + margin: 0pt; +} + +p { + text-align: justify; +} + +hr { + border: 1px solid gray; + background: gray; +} + +h1,h2,h3,h4 { + color: #234623; + font-family: Arial, Sans-serif; +} + +pre { + line-height: 1.0; + color: black; +} + +pre.programlisting { + font-size: 10pt; + padding: 7pt 3pt; + border: 1pt solid black; + background: #eeeeee; + clear: both; +} + +div.table { + margin: 1em; + padding: 0.5em; + text-align: center; +} + +div.table table { + display: table; + width: 100%; +} + +div.table td { + padding-left: 7px; + padding-right: 7px; +} + +.sidebar { + float: right; + margin: 10px 0 10px 30px; + padding: 10px 20px 20px 20px; + width: 33%; + border: 1px solid black; + background-color: #F4F4F4; + font-size: 14px; +} + +.mediaobject { + padding-top: 30px; + padding-bottom: 30px; +} + +.legalnotice { + font-family: Verdana, Arial, helvetica, sans-serif; + font-size: 12px; + font-style: italic; +} + +p.releaseinfo { + font-size: 100%; + font-weight: bold; + font-family: Verdana, Arial, helvetica, sans-serif; + padding-top: 10px; +} + +p.pubdate { + font-size: 120%; + font-weight: bold; + font-family: Verdana, Arial, helvetica, sans-serif; +} + +span.productname { + font-size: 200%; + font-weight: bold; + font-family: Verdana, Arial, helvetica, sans-serif; +} diff --git a/src/docbkx/resources/images/callouts/1.png b/src/docbkx/resources/images/callouts/1.png new file mode 100644 index 00000000..7d473430 Binary files /dev/null and b/src/docbkx/resources/images/callouts/1.png differ diff --git a/src/docbkx/resources/images/callouts/10.png b/src/docbkx/resources/images/callouts/10.png new file mode 100644 index 00000000..997bbc82 Binary files /dev/null and b/src/docbkx/resources/images/callouts/10.png differ diff --git a/src/docbkx/resources/images/callouts/11.png b/src/docbkx/resources/images/callouts/11.png new file mode 100644 index 00000000..ce47dac3 Binary files /dev/null and b/src/docbkx/resources/images/callouts/11.png differ diff --git a/src/docbkx/resources/images/callouts/12.png b/src/docbkx/resources/images/callouts/12.png new file mode 100644 index 00000000..31daf4e2 Binary files /dev/null and b/src/docbkx/resources/images/callouts/12.png differ diff --git a/src/docbkx/resources/images/callouts/13.png b/src/docbkx/resources/images/callouts/13.png new file mode 100644 index 00000000..14021a89 Binary files /dev/null and b/src/docbkx/resources/images/callouts/13.png differ diff --git a/src/docbkx/resources/images/callouts/14.png b/src/docbkx/resources/images/callouts/14.png new file mode 100644 index 00000000..64014b75 Binary files /dev/null and b/src/docbkx/resources/images/callouts/14.png differ diff --git a/src/docbkx/resources/images/callouts/15.png b/src/docbkx/resources/images/callouts/15.png new file mode 100644 index 00000000..0d65765f Binary files /dev/null and b/src/docbkx/resources/images/callouts/15.png differ diff --git a/src/docbkx/resources/images/callouts/2.png b/src/docbkx/resources/images/callouts/2.png new file mode 100644 index 00000000..5d09341b Binary files /dev/null and b/src/docbkx/resources/images/callouts/2.png differ diff --git a/src/docbkx/resources/images/callouts/3.png b/src/docbkx/resources/images/callouts/3.png new file mode 100644 index 00000000..ef7b7004 Binary files /dev/null and b/src/docbkx/resources/images/callouts/3.png differ diff --git a/src/docbkx/resources/images/callouts/4.png b/src/docbkx/resources/images/callouts/4.png new file mode 100644 index 00000000..adb8364e Binary files /dev/null and b/src/docbkx/resources/images/callouts/4.png differ diff --git a/src/docbkx/resources/images/callouts/5.png b/src/docbkx/resources/images/callouts/5.png new file mode 100644 index 00000000..4d7eb460 Binary files /dev/null and b/src/docbkx/resources/images/callouts/5.png differ diff --git a/src/docbkx/resources/images/callouts/6.png b/src/docbkx/resources/images/callouts/6.png new file mode 100644 index 00000000..0ba694af Binary files /dev/null and b/src/docbkx/resources/images/callouts/6.png differ diff --git a/src/docbkx/resources/images/callouts/7.png b/src/docbkx/resources/images/callouts/7.png new file mode 100644 index 00000000..472e96f8 Binary files /dev/null and b/src/docbkx/resources/images/callouts/7.png differ diff --git a/src/docbkx/resources/images/callouts/8.png b/src/docbkx/resources/images/callouts/8.png new file mode 100644 index 00000000..5e60973c Binary files /dev/null and b/src/docbkx/resources/images/callouts/8.png differ diff --git a/src/docbkx/resources/images/callouts/9.png b/src/docbkx/resources/images/callouts/9.png new file mode 100644 index 00000000..a0676d26 Binary files /dev/null and b/src/docbkx/resources/images/callouts/9.png differ diff --git a/src/docbkx/resources/images/logo.png b/src/docbkx/resources/images/logo.png new file mode 100644 index 00000000..a9f6d959 Binary files /dev/null and b/src/docbkx/resources/images/logo.png differ diff --git a/src/docbkx/resources/images/xdev-spring_logo.jpg b/src/docbkx/resources/images/xdev-spring_logo.jpg new file mode 100644 index 00000000..622962ee Binary files /dev/null and b/src/docbkx/resources/images/xdev-spring_logo.jpg differ diff --git a/src/docbkx/resources/xsl/fopdf.xsl b/src/docbkx/resources/xsl/fopdf.xsl new file mode 100644 index 00000000..6ca1a684 --- /dev/null +++ b/src/docbkx/resources/xsl/fopdf.xsl @@ -0,0 +1,418 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Copyright © 2005-2007 + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -5em + -5em + + + + + + + + + + + Spring-WS ( + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 0 + 1 + + 1 + + + + + + book toc + + + + 2 + + + + + + + + + + 0 + 0 + 0 + + + 5mm + 10mm + 10mm + + 15mm + 10mm + 0mm + + 18mm + 18mm + + + 0pc + + + + + justify + false + + + 11 + 8 + + + 1.4 + + + + + + + 0.8em + + + + + + 17.4cm + + + + 4pt + 4pt + 4pt + 4pt + + + + 0.1pt + 0.1pt + + + + + 1 + + + + + + + + left + bold + + + pt + + + + + + + + + + + + + + + 0.8em + 0.8em + 0.8em + + + pt + + 0.1em + 0.1em + 0.1em + + + 0.6em + 0.6em + 0.6em + + + pt + + 0.1em + 0.1em + 0.1em + + + 0.4em + 0.4em + 0.4em + + + pt + + 0.1em + 0.1em + 0.1em + + + + + bold + + + pt + + false + 0.4em + 0.6em + 0.8em + + + + + + + + + pt + + + + + 1em + 1em + 1em + #444444 + solid + 0.1pt + 0.5em + 0.5em + 0.5em + 0.5em + 0.5em + 0.5em + + + + 1 + + #F0F0F0 + + + + + + 0 + 1 + + + 90 + + + + + '1' + + + + + + + figure after + example before + equation before + table before + procedure before + + + + 1 + + + + 0.8em + 0.8em + 0.8em + 0.1em + 0.1em + 0.1em + + + + + + + + + + + + + + + + + diff --git a/src/docbkx/resources/xsl/html.xsl b/src/docbkx/resources/xsl/html.xsl new file mode 100644 index 00000000..aa7930ba --- /dev/null +++ b/src/docbkx/resources/xsl/html.xsl @@ -0,0 +1,91 @@ + + + + + + + + + html.css + + + 1 + 0 + 1 + 0 + + + + + + book toc + + + + 3 + + + + + 1 + + + + + + + 0 + + + 90 + + + + + 0 + + + + + figure after + example before + equation before + table before + procedure before + + + + , + + + + + + + + +
+

Authors

+

+ +

+
+ +
diff --git a/src/docbkx/resources/xsl/html/html_chunk.xsl b/src/docbkx/resources/xsl/html/html_chunk.xsl new file mode 100644 index 00000000..81e6ab23 --- /dev/null +++ b/src/docbkx/resources/xsl/html/html_chunk.xsl @@ -0,0 +1,136 @@ + + + + + + + '5' + + + + 1 + 0 + 1 + + + + images/ + .gif + + 120 + images/callouts/ + .gif + + + css/stylesheet.css + text/css + book toc,title + + text-align: left + + + + + + + + + + + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Begin Google Analytics code + + + End Google Analytics code + + + + + Begin LoopFuse code + + + End LoopFuse code + + + \ No newline at end of file diff --git a/src/docbkx/resources/xsl/html/titlepage.xml b/src/docbkx/resources/xsl/html/titlepage.xml new file mode 100644 index 00000000..09539c06 --- /dev/null +++ b/src/docbkx/resources/xsl/html/titlepage.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + <subtitle/> + <!-- <corpauthor/> + <authorgroup/> + <author/> + <mediaobject/> --> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <hr/> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +</t:templates> diff --git a/src/docbkx/resources/xsl/html_chunk.xsl b/src/docbkx/resources/xsl/html_chunk.xsl new file mode 100644 index 00000000..59016d81 --- /dev/null +++ b/src/docbkx/resources/xsl/html_chunk.xsl @@ -0,0 +1,208 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + This is the XSL HTML configuration file for the Spring Reference Documentation. +--> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:fo="http://www.w3.org/1999/XSL/Format" + version="1.0"> + + <xsl:import href="urn:docbkx:stylesheet"/> + <!--################################################### + HTML Settings + ################################################### --> + <xsl:param name="chunk.section.depth">'5'</xsl:param> + <xsl:param name="use.id.as.filename">'1'</xsl:param> + <!-- These extensions are required for table printing and other stuff --> + <xsl:param name="use.extensions">1</xsl:param> + <xsl:param name="tablecolumns.extension">0</xsl:param> + <xsl:param name="callout.extensions">1</xsl:param> + <xsl:param name="graphicsize.extension">0</xsl:param> + <!--################################################### + Table Of Contents + ################################################### --> + <!-- Generate the TOCs for named components only --> + <xsl:param name="generate.toc"> + book toc + </xsl:param> + <!-- Show only Sections up to level 3 in the TOCs --> + <xsl:param name="toc.section.depth">3</xsl:param> + <!--################################################### + Labels + ################################################### --> + <!-- Label Chapters and Sections (numbering) --> + <xsl:param name="chapter.autolabel">1</xsl:param> + <xsl:param name="section.autolabel" select="1"/> + <xsl:param name="section.label.includes.component.label" select="1"/> + <!--################################################### + Callouts + ################################################### --> + <!-- Place callout marks at this column in annotated areas --> + <xsl:param name="callout.graphics">1</xsl:param> + <xsl:param name="callout.defaultcolumn">90</xsl:param> + <!--################################################### + Misc + ################################################### --> + <!-- Placement of titles --> + <xsl:param name="formal.title.placement"> + figure after + example before + equation before + table before + procedure before + </xsl:param> + <xsl:template match="author" mode="titlepage.mode"> + <xsl:if test="name(preceding-sibling::*[1]) = 'author'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span class="{name(.)}"> + <xsl:call-template name="person.name"/> + <xsl:apply-templates mode="titlepage.mode" select="./contrib"/> + <xsl:apply-templates mode="titlepage.mode" select="./affiliation"/> + </span> + </xsl:template> + <xsl:template match="authorgroup" mode="titlepage.mode"> + <div class="{name(.)}"> + <h2>Authors</h2> + <p/> + <xsl:apply-templates mode="titlepage.mode"/> + </div> + </xsl:template> + <!--################################################### + Headers and Footers + ################################################### --> + <!-- let's have a Spring and SpringSource banner across the top of each page --> + <xsl:template name="user.header.navigation"> + <div style="background-color:white;border:none;height:73px;border:1px solid black;"> + <a style="border:none;" href="http://static.springframework.org/spring-ws/site/" + title="The Spring Framework - Spring Web Services"> + <img style="border:none;" src="images/xdev-spring_logo.jpg"/> + </a> + <a style="border:none;" href="http://www.springsource.com/" title="SpringSource"> + <img style="border:none;position:absolute;padding-top:5px;right:42px;" src="images/s2_box_logo.png"/> + </a> + </div> + </xsl:template> + <!-- no other header navigation (prev, next, etc.) --> + <xsl:template name="header.navigation"/> + <xsl:param name="navig.showtitles">1</xsl:param> + <!-- let's have a 'Sponsored by SpringSource' strapline (or somesuch) across the bottom of each page --> + <xsl:template name="footer.navigation"> + <xsl:param name="prev" select="/foo"/> + <xsl:param name="next" select="/foo"/> + <xsl:param name="nav.context"/> + <xsl:variable name="home" select="/*[1]"/> + <xsl:variable name="up" select="parent::*"/> + <xsl:variable name="row1" select="count($prev) > 0 + or count($up) > 0 + or count($next) > 0"/> + <xsl:variable name="row2" select="($prev and $navig.showtitles != 0) + or (generate-id($home) != generate-id(.) + or $nav.context = 'toc') + or ($chunk.tocs.and.lots != 0 + and $nav.context != 'toc') + or ($next and $navig.showtitles != 0)"/> + <xsl:if test="$suppress.navigation = '0' and $suppress.footer.navigation = '0'"> + <div class="navfooter"> + <xsl:if test="$footer.rule != 0"> + <hr/> + </xsl:if> + <xsl:if test="$row1 or $row2"> + <table width="100%" summary="Navigation footer"> + <xsl:if test="$row1"> + <tr> + <td width="40%" align="left"> + <xsl:if test="count($prev)>0"> + <a accesskey="p"> + <xsl:attribute name="href"> + <xsl:call-template name="href.target"> + <xsl:with-param name="object" select="$prev"/> + </xsl:call-template> + </xsl:attribute> + <xsl:call-template name="navig.content"> + <xsl:with-param name="direction" select="'prev'"/> + </xsl:call-template> + </a> + </xsl:if> + <xsl:text> </xsl:text> + </td> + + <td width="20%" align="center"> + <xsl:choose> + <xsl:when test="$home != . or $nav.context = 'toc'"> + <a accesskey="h"> + <xsl:attribute name="href"> + <xsl:call-template name="href.target"> + <xsl:with-param name="object" select="$home"/> + </xsl:call-template> + </xsl:attribute> + <xsl:call-template name="navig.content"> + <xsl:with-param name="direction" select="'home'"/> + </xsl:call-template> + </a> + <xsl:if test="$chunk.tocs.and.lots != 0 and $nav.context != 'toc'"> + <xsl:text> | </xsl:text> + </xsl:if> + </xsl:when> + <xsl:otherwise> </xsl:otherwise> + </xsl:choose> + <xsl:if test="$chunk.tocs.and.lots != 0 and $nav.context != 'toc'"> + <a accesskey="t"> + <xsl:attribute name="href"> + <xsl:apply-templates select="/*[1]" mode="recursive-chunk-filename"> + <xsl:with-param name="recursive" select="true()"/> + </xsl:apply-templates> + <xsl:text>-toc</xsl:text> + <xsl:value-of select="$html.ext"/> + </xsl:attribute> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'nav-toc'"/> + </xsl:call-template> + </a> + </xsl:if> + </td> + <td width="40%" align="right"> + <xsl:text> </xsl:text> + <xsl:if test="count($next)>0"> + <a accesskey="n"> + <xsl:attribute name="href"> + <xsl:call-template name="href.target"> + <xsl:with-param name="object" select="$next"/> + </xsl:call-template> + </xsl:attribute> + <xsl:call-template name="navig.content"> + <xsl:with-param name="direction" select="'next'"/> + </xsl:call-template> + </a> + </xsl:if> + </td> + </tr> + </xsl:if> + <xsl:if test="$row2"> + <tr> + <td width="40%" align="left" valign="top"> + <xsl:if test="$navig.showtitles != 0"> + <xsl:apply-templates select="$prev" mode="object.title.markup"/> + </xsl:if> + <xsl:text> </xsl:text> + </td> + <td width="20%" align="center"> + <span style="color:white;font-size:90%;"> + <a href="http://www.springsource.com/" + title="SpringSource">Sponsored by SpringSource + </a> + </span> + </td> + <td width="40%" align="right" valign="top"> + <xsl:text> </xsl:text> + <xsl:if test="$navig.showtitles != 0"> + <xsl:apply-templates select="$next" mode="object.title.markup"/> + </xsl:if> + </td> + </tr> + </xsl:if> + </table> + </xsl:if> + </div> + </xsl:if> + </xsl:template> +</xsl:stylesheet> diff --git a/src/docbkx/resources/xsl/pdf/fopdf.xsl b/src/docbkx/resources/xsl/pdf/fopdf.xsl new file mode 100644 index 00000000..2905ee3c --- /dev/null +++ b/src/docbkx/resources/xsl/pdf/fopdf.xsl @@ -0,0 +1,518 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:fo="http://www.w3.org/1999/XSL/Format" + xmlns:xslthl="http://xslthl.sf.net" + exclude-result-prefixes="xslthl" + version='1.0'> + +<!-- Use nice graphics for admonitions --> + <xsl:param name="admon.graphics">'1'</xsl:param> + <xsl:param name="admon.graphics.path">@file.prefix@@dbf.xsl@/images/</xsl:param> + <xsl:param name="draft.watermark.image" select="'@file.prefix@@dbf.xsl@/images/draft.png'"/> + <xsl:param name="paper.type" select="'@paper.type@'"/> + + <xsl:param name="page.margin.top" select="'1cm'"/> + <xsl:param name="region.before.extent" select="'1cm'"/> + <xsl:param name="body.margin.top" select="'1.5cm'"/> + + <xsl:param name="body.margin.bottom" select="'1.5cm'"/> + <xsl:param name="region.after.extent" select="'1cm'"/> + <xsl:param name="page.margin.bottom" select="'1cm'"/> + <xsl:param name="title.margin.left" select="'0cm'"/> + +<!--################################################### + Header + ################################################### --> + +<!-- More space in the center header for long text --> + <xsl:attribute-set name="header.content.properties"> + <xsl:attribute name="font-family"> + <xsl:value-of select="$body.font.family"/> + </xsl:attribute> + <xsl:attribute name="margin-left">-5em</xsl:attribute> + <xsl:attribute name="margin-right">-5em</xsl:attribute> + </xsl:attribute-set> + +<!--################################################### + Table of Contents + ################################################### --> + + <xsl:param name="generate.toc"> + book toc,title + </xsl:param> + +<!--################################################### + Custom Header + ################################################### --> + + <xsl:template name="header.content"> + <xsl:param name="pageclass" select="''"/> + <xsl:param name="sequence" select="''"/> + <xsl:param name="position" select="''"/> + <xsl:param name="gentext-key" select="''"/> + + <xsl:variable name="Version"> + <xsl:choose> + <xsl:when test="//productname"> + <xsl:value-of select="//productname"/><xsl:text> </xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:text>please define productname in your docbook file!</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:choose> + <xsl:when test="$sequence='blank'"> + <xsl:choose> + <xsl:when test="$position='center'"> + <xsl:value-of select="$Version"/> + </xsl:when> + + <xsl:otherwise> + <!-- nop --> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + + <xsl:when test="$pageclass='titlepage'"> + <!-- nop: other titlepage sequences have no header --> + </xsl:when> + + <xsl:when test="$position='center'"> + <xsl:value-of select="$Version"/> + </xsl:when> + + <xsl:otherwise> + <!-- nop --> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + +<!--################################################### + Custom Footer + ################################################### --> + + <xsl:template name="footer.content"> + <xsl:param name="pageclass" select="''"/> + <xsl:param name="sequence" select="''"/> + <xsl:param name="position" select="''"/> + <xsl:param name="gentext-key" select="''"/> + + <xsl:variable name="Version"> + <xsl:choose> + <xsl:when test="//releaseinfo"> + <xsl:value-of select="//releaseinfo"/> + </xsl:when> + <xsl:otherwise> + <!-- nop --> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="Title"> + <xsl:value-of select="//title"/> + </xsl:variable> + + <xsl:choose> + <xsl:when test="$sequence='blank'"> + <xsl:choose> + <xsl:when test="$double.sided != 0 and $position = 'left'"> + <xsl:value-of select="$Version"/> + </xsl:when> + + <xsl:when test="$double.sided = 0 and $position = 'center'"> + <!-- nop --> + </xsl:when> + + <xsl:otherwise> + <fo:page-number/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + + <xsl:when test="$pageclass='titlepage'"> + <!-- nop: other titlepage sequences have no footer --> + </xsl:when> + + <xsl:when test="$double.sided != 0 and $sequence = 'even' and $position='left'"> + <fo:page-number/> + </xsl:when> + + <xsl:when test="$double.sided != 0 and $sequence = 'odd' and $position='right'"> + <fo:page-number/> + </xsl:when> + + <xsl:when test="$double.sided = 0 and $position='right'"> + <fo:page-number/> + </xsl:when> + + <xsl:when test="$double.sided != 0 and $sequence = 'odd' and $position='left'"> + <xsl:value-of select="$Version"/> + </xsl:when> + + <xsl:when test="$double.sided != 0 and $sequence = 'even' and $position='right'"> + <xsl:value-of select="$Version"/> + </xsl:when> + + <xsl:when test="$double.sided = 0 and $position='left'"> + <xsl:value-of select="$Version"/> + </xsl:when> + + <xsl:when test="$position='center'"> + <xsl:value-of select="$Title"/> + </xsl:when> + + <xsl:otherwise> + <!-- nop --> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <xsl:template match="processing-instruction('hard-pagebreak')"> + <fo:block break-before='page'/> + </xsl:template> + +<!--################################################### + Extensions + ################################################### --> + +<!-- These extensions are required for table printing and other stuff --> + <xsl:param name="use.extensions">1</xsl:param> + <xsl:param name="tablecolumns.extension">0</xsl:param> + <xsl:param name="callout.extensions">1</xsl:param> + <xsl:param name="fop.extensions">1</xsl:param> + +<!--################################################### + Paper & Page Size + ################################################### --> + +<!-- Paper type, no headers on blank pages, no double sided printing --> + <xsl:param name="double.sided">0</xsl:param> + <xsl:param name="headers.on.blank.pages">0</xsl:param> + <xsl:param name="footers.on.blank.pages">0</xsl:param> + +<!--################################################### + Fonts & Styles + ################################################### --> + + <xsl:param name="hyphenate">false</xsl:param> + +<!-- Default Font size --> + <xsl:param name="body.font.master">11</xsl:param> + <xsl:param name="body.font.small">8</xsl:param> + +<!-- Line height in body text --> + <xsl:param name="line-height">1.4</xsl:param> + +<!-- Chapter title size --> + <xsl:attribute-set name="chapter.titlepage.recto.style"> + <xsl:attribute name="text-align">left</xsl:attribute> + <xsl:attribute name="font-weight">bold</xsl:attribute> + <xsl:attribute name="font-size"> + <xsl:value-of select="$body.font.master * 1.8"/> + <xsl:text>pt</xsl:text> + </xsl:attribute> + </xsl:attribute-set> + +<!-- Why is the font-size for chapters hardcoded in the XSL FO templates? + Let's remove it, so this sucker can use our attribute-set only... --> + <xsl:template match="title" mode="chapter.titlepage.recto.auto.mode"> + <fo:block xmlns:fo="http://www.w3.org/1999/XSL/Format" + xsl:use-attribute-sets="chapter.titlepage.recto.style"> + <xsl:call-template name="component.title"> + <xsl:with-param name="node" select="ancestor-or-self::chapter[1]"/> + </xsl:call-template> + </fo:block> + </xsl:template> + +<!-- Sections 1, 2 and 3 titles have a small bump factor and padding --> + <xsl:attribute-set name="section.title.level1.properties"> + <xsl:attribute name="space-before.optimum">0.8em</xsl:attribute> + <xsl:attribute name="space-before.minimum">0.8em</xsl:attribute> + <xsl:attribute name="space-before.maximum">0.8em</xsl:attribute> + <xsl:attribute name="font-size"> + <xsl:value-of select="$body.font.master * 1.5"/> + <xsl:text>pt</xsl:text> + </xsl:attribute> + <xsl:attribute name="space-after.optimum">0.1em</xsl:attribute> + <xsl:attribute name="space-after.minimum">0.1em</xsl:attribute> + <xsl:attribute name="space-after.maximum">0.1em</xsl:attribute> + </xsl:attribute-set> + <xsl:attribute-set name="section.title.level2.properties"> + <xsl:attribute name="space-before.optimum">0.6em</xsl:attribute> + <xsl:attribute name="space-before.minimum">0.6em</xsl:attribute> + <xsl:attribute name="space-before.maximum">0.6em</xsl:attribute> + <xsl:attribute name="font-size"> + <xsl:value-of select="$body.font.master * 1.25"/> + <xsl:text>pt</xsl:text> + </xsl:attribute> + <xsl:attribute name="space-after.optimum">0.1em</xsl:attribute> + <xsl:attribute name="space-after.minimum">0.1em</xsl:attribute> + <xsl:attribute name="space-after.maximum">0.1em</xsl:attribute> + </xsl:attribute-set> + <xsl:attribute-set name="section.title.level3.properties"> + <xsl:attribute name="space-before.optimum">0.4em</xsl:attribute> + <xsl:attribute name="space-before.minimum">0.4em</xsl:attribute> + <xsl:attribute name="space-before.maximum">0.4em</xsl:attribute> + <xsl:attribute name="font-size"> + <xsl:value-of select="$body.font.master * 1.0"/> + <xsl:text>pt</xsl:text> + </xsl:attribute> + <xsl:attribute name="space-after.optimum">0.1em</xsl:attribute> + <xsl:attribute name="space-after.minimum">0.1em</xsl:attribute> + <xsl:attribute name="space-after.maximum">0.1em</xsl:attribute> + </xsl:attribute-set> + <xsl:attribute-set name="section.title.level4.properties"> + <xsl:attribute name="space-before.optimum">0.3em</xsl:attribute> + <xsl:attribute name="space-before.minimum">0.3em</xsl:attribute> + <xsl:attribute name="space-before.maximum">0.3em</xsl:attribute> + <xsl:attribute name="font-size"> + <xsl:value-of select="$body.font.master * 0.9"/> + <xsl:text>pt</xsl:text> + </xsl:attribute> + <xsl:attribute name="space-after.optimum">0.1em</xsl:attribute> + <xsl:attribute name="space-after.minimum">0.1em</xsl:attribute> + <xsl:attribute name="space-after.maximum">0.1em</xsl:attribute> + </xsl:attribute-set> + +<!-- Use code syntax highlighting --> + <xsl:param name="highlight.source" select="1"/> + <xsl:param name="highlight.default.language" select="xml" /> + + <xsl:template match='xslthl:keyword'> + <fo:inline font-weight="bold" color="#7F0055"><xsl:apply-templates/></fo:inline> + </xsl:template> + + <xsl:template match='xslthl:comment'> + <fo:inline font-style="italic" color="#3F5F5F"><xsl:apply-templates/></fo:inline> + </xsl:template> + + <xsl:template match='xslthl:oneline-comment'> + <fo:inline font-style="italic" color="#3F5F5F"><xsl:apply-templates/></fo:inline> + </xsl:template> + + <xsl:template match='xslthl:multiline-comment'> + <fo:inline font-style="italic" color="#3F5FBF"><xsl:apply-templates/></fo:inline> + </xsl:template> + + <xsl:template match='xslthl:tag'> + <fo:inline color="#3F7F7F"><xsl:apply-templates/></fo:inline> + </xsl:template> + + <xsl:template match='xslthl:attribute'> + <fo:inline color="#7F007F"><xsl:apply-templates/></fo:inline> + </xsl:template> + + <xsl:template match='xslthl:value'> + <fo:inline color="#2A00FF"><xsl:apply-templates/></fo:inline> + </xsl:template> + + <xsl:template match='xslthl:string'> + <fo:inline color="#2A00FF"><xsl:apply-templates/></fo:inline> + </xsl:template> + +<!--################################################### + Tables + ################################################### --> + + <!-- Some padding inside tables --> + <xsl:attribute-set name="table.cell.padding"> + <xsl:attribute name="padding-left">4pt</xsl:attribute> + <xsl:attribute name="padding-right">4pt</xsl:attribute> + <xsl:attribute name="padding-top">4pt</xsl:attribute> + <xsl:attribute name="padding-bottom">4pt</xsl:attribute> + </xsl:attribute-set> + +<!-- Only hairlines as frame and cell borders in tables --> + <xsl:param name="table.frame.border.thickness">0.1pt</xsl:param> + <xsl:param name="table.cell.border.thickness">0.1pt</xsl:param> + +<!--################################################### + Labels + ################################################### --> + +<!-- Label Chapters and Sections (numbering) --> + <xsl:param name="chapter.autolabel" select="1"/> + <xsl:param name="section.autolabel" select="1"/> + <xsl:param name="section.autolabel.max.depth" select="1"/> + + <xsl:param name="section.label.includes.component.label" select="1"/> + <xsl:param name="table.footnote.number.format" select="'1'"/> + +<!--################################################### + Programlistings + ################################################### --> + +<!-- Verbatim text formatting (programlistings) --> + <xsl:attribute-set name="monospace.verbatim.properties"> + <xsl:attribute name="font-size"> + <xsl:value-of select="$body.font.small * 1.0"/> + <xsl:text>pt</xsl:text> + </xsl:attribute> + </xsl:attribute-set> + + <xsl:attribute-set name="verbatim.properties"> + <xsl:attribute name="space-before.minimum">1em</xsl:attribute> + <xsl:attribute name="space-before.optimum">1em</xsl:attribute> + <xsl:attribute name="space-before.maximum">1em</xsl:attribute> + <xsl:attribute name="space-after.minimum">0.1em</xsl:attribute> + <xsl:attribute name="space-after.optimum">0.1em</xsl:attribute> + <xsl:attribute name="space-after.maximum">0.1em</xsl:attribute> + + <xsl:attribute name="border-color">#444444</xsl:attribute> + <xsl:attribute name="border-style">solid</xsl:attribute> + <xsl:attribute name="border-width">0.1pt</xsl:attribute> + <xsl:attribute name="padding-top">0.5em</xsl:attribute> + <xsl:attribute name="padding-left">0.5em</xsl:attribute> + <xsl:attribute name="padding-right">0.5em</xsl:attribute> + <xsl:attribute name="padding-bottom">0.5em</xsl:attribute> + <xsl:attribute name="margin-left">0.5em</xsl:attribute> + <xsl:attribute name="margin-right">0.5em</xsl:attribute> + </xsl:attribute-set> + + <!-- Shade (background) programlistings --> + <xsl:param name="shade.verbatim">1</xsl:param> + <xsl:attribute-set name="shade.verbatim.style"> + <xsl:attribute name="background-color">#F0F0F0</xsl:attribute> + </xsl:attribute-set> + + <xsl:attribute-set name="list.block.spacing"> + <xsl:attribute name="space-before.optimum">0.1em</xsl:attribute> + <xsl:attribute name="space-before.minimum">0.1em</xsl:attribute> + <xsl:attribute name="space-before.maximum">0.1em</xsl:attribute> + <xsl:attribute name="space-after.optimum">0.1em</xsl:attribute> + <xsl:attribute name="space-after.minimum">0.1em</xsl:attribute> + <xsl:attribute name="space-after.maximum">0.1em</xsl:attribute> + </xsl:attribute-set> + + <xsl:attribute-set name="example.properties"> + <xsl:attribute name="space-before.minimum">0.5em</xsl:attribute> + <xsl:attribute name="space-before.optimum">0.5em</xsl:attribute> + <xsl:attribute name="space-before.maximum">0.5em</xsl:attribute> + <xsl:attribute name="space-after.minimum">0.1em</xsl:attribute> + <xsl:attribute name="space-after.optimum">0.1em</xsl:attribute> + <xsl:attribute name="space-after.maximum">0.1em</xsl:attribute> + <xsl:attribute name="keep-together.within-column">always</xsl:attribute> + </xsl:attribute-set> + +<!--################################################### + Title information for Figures, Examples etc. + ################################################### --> + + <xsl:attribute-set name="formal.title.properties" use-attribute-sets="normal.para.spacing"> + <xsl:attribute name="font-weight">normal</xsl:attribute> + <xsl:attribute name="font-style">italic</xsl:attribute> + <xsl:attribute name="font-size"> + <xsl:value-of select="$body.font.master"/> + <xsl:text>pt</xsl:text> + </xsl:attribute> + <xsl:attribute name="hyphenate">false</xsl:attribute> + <xsl:attribute name="space-before.minimum">0.1em</xsl:attribute> + <xsl:attribute name="space-before.optimum">0.1em</xsl:attribute> + <xsl:attribute name="space-before.maximum">0.1em</xsl:attribute> + </xsl:attribute-set> + +<!--################################################### + Callouts + ################################################### --> + +<!-- don't use images for callouts --> + <xsl:param name="callout.graphics">0</xsl:param> + <xsl:param name="callout.unicode">1</xsl:param> + +<!-- Place callout marks at this column in annotated areas --> + <xsl:param name="callout.defaultcolumn">90</xsl:param> + +<!--################################################### + Misc + ################################################### --> + +<!-- Placement of titles --> + <xsl:param name="formal.title.placement"> + figure after + example after + equation before + table before + procedure before + </xsl:param> + +<!-- Format Variable Lists as Blocks (prevents horizontal overflow) --> + <xsl:param name="variablelist.as.blocks">1</xsl:param> + + <xsl:param name="body.start.indent">0pt</xsl:param> + +<!-- Show only Sections up to level 3 in the TOCs --> + <xsl:param name="toc.section.depth">3</xsl:param> + +<!-- Remove "Chapter" from the Chapter titles... --> + <xsl:param name="local.l10n.xml" select="document('')"/> + <l:i18n xmlns:l="http://docbook.sourceforge.net/xmlns/l10n/1.0"> + <l:l10n language="en"> + <l:context name="title-numbered"> + <l:template name="chapter" text="%n. %t"/> + <l:template name="section" text="%n %t"/> + </l:context> + <l:context name="title"> + <l:template name="example" text="Example %n %t"/> + </l:context> + </l:l10n> + </l:i18n> + +<!--################################################### + colored and hyphenated links + ################################################### --> + + <xsl:template match="ulink"> + <fo:basic-link external-destination="{@url}" + xsl:use-attribute-sets="xref.properties" + text-decoration="underline" + color="blue"> + <xsl:choose> + <xsl:when test="count(child::node())=0"> + <xsl:value-of select="@url"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates/> + </xsl:otherwise> + </xsl:choose> + </fo:basic-link> + </xsl:template> + + <xsl:template match="link"> + <fo:basic-link internal-destination="{@linkend}" + xsl:use-attribute-sets="xref.properties" + text-decoration="underline" + color="blue"> + <xsl:choose> + <xsl:when test="count(child::node())=0"> + <xsl:value-of select="@linkend"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates/> + </xsl:otherwise> + </xsl:choose> + </fo:basic-link> + </xsl:template> + +</xsl:stylesheet> \ No newline at end of file diff --git a/src/docbkx/resources/xsl/pdf/titlepage.xml b/src/docbkx/resources/xsl/pdf/titlepage.xml new file mode 100644 index 00000000..dc18e1e0 --- /dev/null +++ b/src/docbkx/resources/xsl/pdf/titlepage.xml @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +<!DOCTYPE t:templates [ +<!ENTITY hsize0 "10pt"> +<!ENTITY hsize1 "12pt"> +<!ENTITY hsize2 "14.4pt"> +<!ENTITY hsize3 "17.28pt"> +<!ENTITY hsize4 "20.736pt"> +<!ENTITY hsize5 "24.8832pt"> +<!ENTITY hsize0space "7.5pt"> <!-- 0.75 * hsize0 --> +<!ENTITY hsize1space "9pt"> <!-- 0.75 * hsize1 --> +<!ENTITY hsize2space "10.8pt"> <!-- 0.75 * hsize2 --> +<!ENTITY hsize3space "12.96pt"> <!-- 0.75 * hsize3 --> +<!ENTITY hsize4space "15.552pt"> <!-- 0.75 * hsize4 --> +<!ENTITY hsize5space "18.6624pt"> <!-- 0.75 * hsize5 --> +]> +<t:templates xmlns:t="http://nwalsh.com/docbook/xsl/template/1.0" + xmlns:param="http://nwalsh.com/docbook/xsl/template/1.0/param" + xmlns:fo="http://www.w3.org/1999/XSL/Format" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + + <t:titlepage t:element="book" t:wrapper="fo:block"> + <t:titlepage-content t:side="recto"> + <title + t:named-template="division.title" + param:node="ancestor-or-self::book[1]" + text-align="center" + font-size="&hsize5;" + space-before="&hsize5space;" + font-weight="bold" + font-family="{$title.fontset}"/> + <subtitle + text-align="center" + font-size="&hsize4;" + space-before="&hsize4space;" + font-family="{$title.fontset}"/> + + <!-- <corpauthor space-before="0.5em" + font-size="&hsize2;"/> + <authorgroup space-before="0.5em" + font-size="&hsize2;"/> + <author space-before="0.5em" + font-size="&hsize2;"/> --> + + <mediaobject space-before="2em" space-after="2em"/> + <releaseinfo space-before="5em" font-size="&hsize2;"/> + <copyright space-before="1.5em" + font-weight="normal" + font-size="8"/> + <legalnotice space-before="5em" + font-weight="normal" + font-style="italic" + font-size="8"/> + <othercredit space-before="2em" + font-weight="normal" + font-size="8"/> + <pubdate space-before="0.5em"/> + <revision space-before="0.5em"/> + <revhistory space-before="0.5em"/> + <abstract space-before="0.5em" + text-align="start" + margin-left="0.5in" + margin-right="0.5in" + font-family="{$body.fontset}"/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +</t:templates> diff --git a/src/docbkx/sample-apps.xml b/src/docbkx/sample-apps.xml new file mode 100644 index 00000000..b01d2bac --- /dev/null +++ b/src/docbkx/sample-apps.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" +"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"> +<chapter id="sample-apps"> + <title>Sample Applications + +
+ Introduction + + Introduction +
+ +
+ Hello World + +
+ Direct Exchange - P2P + + Direct exchange +
+ +
+ Topic Exchange - Pub/Sub + + Topic Exchange +
+
+ +
+ Stock Trading + + Stock Trading +
+ diff --git a/src/docbkx/si-amqp.xml b/src/docbkx/si-amqp.xml new file mode 100644 index 00000000..e0ec6624 --- /dev/null +++ b/src/docbkx/si-amqp.xml @@ -0,0 +1,48 @@ + + + + Spring Integration AMQP Support + +
+ Introduction + + AMQP support +
+ +
+ Inbound Channel Adapter + + Introduction.... +
+ +
+ Message-Driven Channel Adapter + + message-driven channel adapter +
+ +
+ Outbound Channel Adapter + + outbound channel adapter +
+ +
+ Inbound Gateway + + inbound gateway +
+ +
+ Outbound Gateway + + outbound gateway +
+ +
+ AMQP Samples + + samples +
+
diff --git a/src/main/javadoc/spring-javadoc.css b/src/main/javadoc/spring-javadoc.css new file mode 100644 index 00000000..c438fafa --- /dev/null +++ b/src/main/javadoc/spring-javadoc.css @@ -0,0 +1,178 @@ +/* stylesheet.css 2008/04/22 nicolekonicki */ + +/* + * + * Spring-specific Javadoc style sheet + * + */ + + + +.code +{ + border: 1px solid black; + background-color: #F4F4F4; + padding: 5px; +} + +body +{ + font: 12px Verdana, Arial, Helvetica, "Bitstream Vera Sans", sans-serif; + background-color: #fff; + color: #333; +} + + +/* Link colors */ +a +{ + color:#2c7b14; + text-decoration:none; +} + +a:hover +{ + text-decoration:underline; +} + +/* Headings */ +h1 +{ + font-size:28px; + color:#007c00; +} + +/* Table colors */ + +table +{ + border:none; +} + +td +{ + border:none; + border-bottom:1px dotted #ddd; +} + +th +{ + border:none; +} + +.TableHeadingColor th +{ + background-color: #efffcb; + background-image: url(doc-files/th-background.png); + background-repeat: repeat-x; + color:#fff; + font-size:14px; + height:26px; +} + +.TableSubHeadingColor +{ + background: #f7ffee; + +} +.TableRowColor +{ + background: #fff; +} + +.TableRowColor a +{ + border-bottom:none; + color:#2c7b14; + font-weight:normal; +} + +tr.TableRowColor:hover +{ + background:#eef2e1; +} + + +/* Font used in left-hand frame lists */ +.FrameTitleFont +{ + font-size: 120%; + font-weight:bold; +} + +.FrameTitleFont a +{ + color: #333; +} + +.FrameHeadingFont +{ + font-weight: bold; + font-size:95%; +} + +.FrameItemFont +{ + line-height:130%; + font-size: 95%; +} + +.FrameItemFont a +{ + color:#333; +} + +.FrameItemFont a:hover +{ + color:#249901; + border-bottom:none; + text-decoration:underline; +} + +/* Navigation bar fonts and colors */ +.NavBarCell1 +{ + background-color:#fff; + border:none; +} + +.NavBarCell1Rev +{ + background-color:#e3faa5; + border:1px solid #9ad00c; + padding:0; + margin:0; +} + +.NavBarCell1 a +{ + color:#333; + text-decoration:none; +} + +.NavBarFont1Rev +{ + +} + +.NavBarCell2 +{ + border:none; +} + +.NavBarCell2 a +{ + color:#249901; + font-size:90%; +} + +.NavBarCell3 +{ + border:none; +} + +/* Override sizes in font tags */ +font +{ + font: inherit !important; +} diff --git a/src/main/resources/apache-license.txt b/src/main/resources/apache-license.txt new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/src/main/resources/apache-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/src/main/resources/changelog.txt b/src/main/resources/changelog.txt new file mode 100644 index 00000000..9b30ad27 --- /dev/null +++ b/src/main/resources/changelog.txt @@ -0,0 +1,2 @@ +SPRING AMQP CHANGELOG +============================ \ No newline at end of file diff --git a/src/main/resources/epl-license.txt b/src/main/resources/epl-license.txt new file mode 100644 index 00000000..41808a4f --- /dev/null +++ b/src/main/resources/epl-license.txt @@ -0,0 +1,289 @@ +ERLANG PUBLIC LICENSE +Version 1.0 + +1. Definitions. + +1.1. ``Contributor'' means each entity that creates or contributes to +the creation of Modifications. + +1.2. ``Contributor Version'' means the combination of the Original +Code, prior Modifications used by a Contributor, and the Modifications +made by that particular Contributor. + +1.3. ``Covered Code'' means the Original Code or Modifications or the +combination of the Original Code and Modifications, in each case +including portions thereof. + +1.4. ``Electronic Distribution Mechanism'' means a mechanism generally +accepted in the software development community for the electronic +transfer of data. + +1.5. ``Executable'' means Covered Code in any form other than Source +Code. + +1.6. ``Initial Developer'' means the individual or entity identified +as the Initial Developer in the Source Code notice required by Exhibit +A. + +1.7. ``Larger Work'' means a work which combines Covered Code or +portions thereof with code not governed by the terms of this License. + +1.8. ``License'' means this document. + +1.9. ``Modifications'' means any addition to or deletion from the +substance or structure of either the Original Code or any previous +Modifications. When Covered Code is released as a series of files, a +Modification is: + +A. Any addition to or deletion from the contents of a file containing + Original Code or previous Modifications. + +B. Any new file that contains any part of the Original Code or + previous Modifications. + +1.10. ``Original Code'' means Source Code of computer software code +which is described in the Source Code notice required by Exhibit A as +Original Code, and which, at the time of its release under this +License is not already Covered Code governed by this License. + +1.11. ``Source Code'' means the preferred form of the Covered Code for +making modifications to it, including all modules it contains, plus +any associated interface definition files, scripts used to control +compilation and installation of an Executable, or a list of source +code differential comparisons against either the Original Code or +another well known, available Covered Code of the Contributor's +choice. The Source Code can be in a compressed or archival form, +provided the appropriate decompression or de-archiving software is +widely available for no charge. + +1.12. ``You'' means an individual or a legal entity exercising rights +under, and complying with all of the terms of, this License. For legal +entities,``You'' includes any entity which controls, is controlled by, +or is under common control with You. For purposes of this definition, +``control'' means (a) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or +otherwise, or (b) ownership of fifty percent (50%) or more of the +outstanding shares or beneficial ownership of such entity. + +2. Source Code License. + +2.1. The Initial Developer Grant. +The Initial Developer hereby grants You a world-wide, royalty-free, +non-exclusive license, subject to third party intellectual property +claims: + +(a) to use, reproduce, modify, display, perform, sublicense and + distribute the Original Code (or portions thereof) with or without + Modifications, or as part of a Larger Work; and + +(b) under patents now or hereafter owned or controlled by Initial + Developer, to make, have made, use and sell (``Utilize'') the + Original Code (or portions thereof), but solely to the extent that + any such patent is reasonably necessary to enable You to Utilize + the Original Code (or portions thereof) and not to any greater + extent that may be necessary to Utilize further Modifications or + combinations. + +2.2. Contributor Grant. +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license, subject to third party intellectual property +claims: + +(a) to use, reproduce, modify, display, perform, sublicense and + distribute the Modifications created by such Contributor (or + portions thereof) either on an unmodified basis, with other + Modifications, as Covered Code or as part of a Larger Work; and + +(b) under patents now or hereafter owned or controlled by Contributor, + to Utilize the Contributor Version (or portions thereof), but + solely to the extent that any such patent is reasonably necessary + to enable You to Utilize the Contributor Version (or portions + thereof), and not to any greater extent that may be necessary to + Utilize further Modifications or combinations. + +3. Distribution Obligations. + +3.1. Application of License. +The Modifications which You contribute are governed by the terms of +this License, including without limitation Section 2.2. The Source +Code version of Covered Code may be distributed only under the terms +of this License, and You must include a copy of this License with +every copy of the Source Code You distribute. You may not offer or +impose any terms on any Source Code version that alters or restricts +the applicable version of this License or the recipients' rights +hereunder. However, You may include an additional document offering +the additional rights described in Section 3.5. + +3.2. Availability of Source Code. +Any Modification which You contribute must be made available in Source +Code form under the terms of this License either on the same media as +an Executable version or via an accepted Electronic Distribution +Mechanism to anyone to whom you made an Executable version available; +and if made available via Electronic Distribution Mechanism, must +remain available for at least twelve (12) months after the date it +initially became available, or at least six (6) months after a +subsequent version of that particular Modification has been made +available to such recipients. You are responsible for ensuring that +the Source Code version remains available even if the Electronic +Distribution Mechanism is maintained by a third party. + +3.3. Description of Modifications. +You must cause all Covered Code to which you contribute to contain a +file documenting the changes You made to create that Covered Code and +the date of any change. You must include a prominent statement that +the Modification is derived, directly or indirectly, from Original +Code provided by the Initial Developer and including the name of the +Initial Developer in (a) the Source Code, and (b) in any notice in an +Executable version or related documentation in which You describe the +origin or ownership of the Covered Code. + +3.4. Intellectual Property Matters + +(a) Third Party Claims. + If You have knowledge that a party claims an intellectual property + right in particular functionality or code (or its utilization + under this License), you must include a text file with the source + code distribution titled ``LEGAL'' which describes the claim and + the party making the claim in sufficient detail that a recipient + will know whom to contact. If you obtain such knowledge after You + make Your Modification available as described in Section 3.2, You + shall promptly modify the LEGAL file in all copies You make + available thereafter and shall take other steps (such as notifying + appropriate mailing lists or newsgroups) reasonably calculated to + inform those who received the Covered Code that new knowledge has + been obtained. + +(b) Contributor APIs. + If Your Modification is an application programming interface and + You own or control patents which are reasonably necessary to + implement that API, you must also include this information in the + LEGAL file. + +3.5. Required Notices. +You must duplicate the notice in Exhibit A in each file of the Source +Code, and this License in any documentation for the Source Code, where +You describe recipients' rights relating to Covered Code. If You +created one or more Modification(s), You may add your name as a +Contributor to the notice described in Exhibit A. If it is not +possible to put such notice in a particular Source Code file due to +its structure, then you must include such notice in a location (such +as a relevant directory file) where a user would be likely to look for +such a notice. You may choose to offer, and to charge a fee for, +warranty, support, indemnity or liability obligations to one or more +recipients of Covered Code. However, You may do so only on Your own +behalf, and not on behalf of the Initial Developer or any +Contributor. You must make it absolutely clear than any such warranty, +support, indemnity or liability obligation is offered by You alone, +and You hereby agree to indemnify the Initial Developer and every +Contributor for any liability incurred by the Initial Developer or +such Contributor as a result of warranty, support, indemnity or +liability terms You offer. + +3.6. Distribution of Executable Versions. +You may distribute Covered Code in Executable form only if the +requirements of Section 3.1-3.5 have been met for that Covered Code, +and if You include a notice stating that the Source Code version of +the Covered Code is available under the terms of this License, +including a description of how and where You have fulfilled the +obligations of Section 3.2. The notice must be conspicuously included +in any notice in an Executable version, related documentation or +collateral in which You describe recipients' rights relating to the +Covered Code. You may distribute the Executable version of Covered +Code under a license of Your choice, which may contain terms different +from this License, provided that You are in compliance with the terms +of this License and that the license for the Executable version does +not attempt to limit or alter the recipient's rights in the Source +Code version from the rights set forth in this License. If You +distribute the Executable version under a different license You must +make it absolutely clear that any terms which differ from this License +are offered by You alone, not by the Initial Developer or any +Contributor. You hereby agree to indemnify the Initial Developer and +every Contributor for any liability incurred by the Initial Developer +or such Contributor as a result of any such terms You offer. + +3.7. Larger Works. +You may create a Larger Work by combining Covered Code with other code +not governed by the terms of this License and distribute the Larger +Work as a single product. In such a case, You must make sure the +requirements of this License are fulfilled for the Covered Code. + +4. Inability to Comply Due to Statute or Regulation. +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Code due to statute +or regulation then You must: (a) comply with the terms of this License +to the maximum extent possible; and (b) describe the limitations and +the code they affect. Such description must be included in the LEGAL +file described in Section 3.4 and must be included with all +distributions of the Source Code. Except to the extent prohibited by +statute or regulation, such description must be sufficiently detailed +for a recipient of ordinary skill to be able to understand it. + +5. Application of this License. + +This License applies to code to which the Initial Developer has +attached the notice in Exhibit A, and to related Covered Code. + +6. CONNECTION TO MOZILLA PUBLIC LICENSE + +This Erlang License is a derivative work of the Mozilla Public +License, Version 1.0. It contains terms which differ from the Mozilla +Public License, Version 1.0. + +7. DISCLAIMER OF WARRANTY. + +COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN ``AS IS'' BASIS, +WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, +WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF +DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR +NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF +THE COVERED CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE +IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER +CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR +CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART +OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER +EXCEPT UNDER THIS DISCLAIMER. + +8. TERMINATION. +This License and the rights granted hereunder will terminate +automatically if You fail to comply with terms herein and fail to cure +such breach within 30 days of becoming aware of the breach. All +sublicenses to the Covered Code which are properly granted shall +survive any termination of this License. Provisions which, by their +nature, must remain in effect beyond the termination of this License +shall survive. + +9. DISCLAIMER OF LIABILITY +Any utilization of Covered Code shall not cause the Initial Developer +or any Contributor to be liable for any damages (neither direct nor +indirect). + +10. MISCELLANEOUS +This License represents the complete agreement concerning the subject +matter hereof. If any provision is held to be unenforceable, such +provision shall be reformed only to the extent necessary to make it +enforceable. This License shall be construed by and in accordance with +the substantive laws of Sweden. Any dispute, controversy or claim +arising out of or relating to this License, or the breach, termination +or invalidity thereof, shall be subject to the exclusive jurisdiction +of Swedish courts, with the Stockholm City Court as the first +instance. + +EXHIBIT A. + +``The contents of this file are subject to the Erlang Public License, +Version 1.0, (the "License"); you may not use this file except in +compliance with the License. You may obtain a copy of the License at +http://www.eddieware.org/EPL + +Software distributed under the License is distributed on an "AS IS" +basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +the License for the specific language governing rights and limitations +under the License. + +The Original Code is Eddie, November, 1998. + +The Initial Developer of the Original Code is Ericsson Telecom +AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson +Telecom AB. All Rights Reserved. + +Contributor(s): ______________________________________.'' diff --git a/src/main/resources/notice.txt b/src/main/resources/notice.txt new file mode 100644 index 00000000..f62045a2 --- /dev/null +++ b/src/main/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 + enquiries@springsource.com. diff --git a/src/main/resources/readme.txt b/src/main/resources/readme.txt new file mode 100644 index 00000000..eaef1c1f --- /dev/null +++ b/src/main/resources/readme.txt @@ -0,0 +1,27 @@ +SPRING AMQP INTEGRATION 1.0.0 M1 (May 12 2010) +------------------------------- + +home page: http://www.springsource.org/extensions/se-amqp +forum : http://forum.springsource.org/forumdisplay.php?f=74 + +1. INTRODUCTION + + +2. RELEASE INFO + +Release contents: + +Spring AMQP is released under the terms of the Apache Software License (see license.txt). + +3. DISTRIBUTION JAR FILES + +4. WHERE TO START + +This distribution contains documentation and two sample applications illustrating the features of Spring AMQP + +To build you need the apache qpid libraries. As they are not yet available in a public maven repositories, the solution is to add them manually to your private maven repository. It can be done by executing: + +mvn install:install-file -DgroupId=jinterface -DartifactId=jinterface -Dversion=1.4.2 -Dpackaging=maven-plugin -Dfile=OtpErlang.jar + +5. ADDITIONAL RESOURCES +