├── .gitignore
├── LICENSE
├── README.textile
├── project
├── ScalazCamelBuild.scala
└── plugins.sbt
├── scalaz-camel-akka
└── src
│ ├── main
│ └── scala
│ │ ├── akka
│ │ └── actor
│ │ │ └── Sender.scala
│ │ └── scalaz
│ │ └── camel
│ │ └── akka
│ │ ├── ActorMgnt.scala
│ │ ├── ActorRefx.scala
│ │ ├── Akka.scala
│ │ ├── Conv.scala
│ │ └── Dsl.scala
│ └── test
│ ├── resources
│ ├── akka.conf
│ └── logback-test.xml
│ └── scala
│ └── scalaz
│ └── camel
│ └── akka
│ ├── AkkaTest.scala
│ ├── AkkaTestContext.scala
│ └── AkkaTestProcessors.scala
├── scalaz-camel-core
└── src
│ ├── main
│ └── scala
│ │ └── scalaz
│ │ └── camel
│ │ └── core
│ │ ├── Camel.scala
│ │ ├── Conv.scala
│ │ ├── Dsl.scala
│ │ ├── Message.scala
│ │ └── Router.scala
│ └── test
│ ├── resources
│ └── context.xml
│ └── scala
│ └── scalaz
│ └── camel
│ └── core
│ ├── CamelAttemptTest.scala
│ ├── CamelJettyTest.scala
│ ├── CamelJmsTest.scala
│ ├── CamelLoadTest.scala
│ ├── CamelSetupTest.scala
│ ├── CamelTest.scala
│ ├── CamelTestContext.scala
│ ├── CamelTestProcessors.scala
│ ├── ExecutorMgnt.scala
│ ├── MessageTest.scala
│ └── RouterTest.scala
└── scalaz-camel-samples
└── src
├── main
├── resources
│ └── context.xml
└── scala
│ └── scalaz
│ └── camel
│ └── samples
│ └── CoreExample.scala
└── test
└── scala
└── scalaz
└── camel
└── samples
└── CoreExampleTest.scala
/.gitignore:
--------------------------------------------------------------------------------
1 | activemq-data
2 | scalaz-camel.*
3 | project/boot
4 | project/build/target
5 | project/plugins/project
6 | project/plugins/target
7 | lib_managed
8 | target
9 | .idea
10 | *.iml
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.textile:
--------------------------------------------------------------------------------
1 | h2. Update
2 |
3 | Scalaz-camel is now superseded by the "streamz":https://github.com/krasserm/streamz project and its "combinators for Apache Camel":https://github.com/krasserm/streamz#combinators-for-apache-camel.
4 |
5 | h2. Introduction
6 |
7 | This project provides a domain-specific language (DSL) for "Apache Camel":http://camel.apache.org/ that is based on the "Scala":http://www.scala-lang.org/ programming language and the "Scalaz":http://code.google.com/p/scalaz/ library. It applies functional programming (FP) concepts to "enterprise integration patterns":http://www.enterpriseintegrationpatterns.com/ (EIPs) so that integration solutions can be constructed by composing EIPs with a functional DSL.
8 |
9 | The scalaz-camel DSL is an alternative to Camel's existing Java DSL and Scala DSL. It was re-designed from scratch with the goal of a better and more native integration with the Scala programming language.
10 |
11 | "Learn more ...":https://github.com/krasserm/scalaz-camel/wiki
12 |
13 |
--------------------------------------------------------------------------------
/project/ScalazCamelBuild.scala:
--------------------------------------------------------------------------------
1 | import sbt._
2 | import Keys._
3 |
4 | object ScalazCamelBuild extends Build {
5 |
6 | lazy val buildSettings = Seq(
7 | organization := "scalaz.camel",
8 | version := "0.4-SNAPSHOT",
9 | scalaVersion := "2.9.2"
10 | )
11 |
12 | override lazy val settings = super.settings ++ buildSettings
13 | lazy val baseSettings = Defaults.defaultSettings
14 | lazy val defaultSettings = baseSettings ++ Seq(
15 | resolvers += "Typesafe Repo" at "http://repo.typesafe.com/typesafe/releases/",
16 | resolvers += "Akka Repository" at "http://akka.io/repository",
17 | // compile options
18 | scalacOptions ++= Seq("-encoding", "UTF-8", "-deprecation", "-unchecked") ++ (
19 | if (true || (System getProperty "java.runtime.version" startsWith "1.7")) Seq() else Seq("-optimize")), // -optimize fails with jdk7
20 | javacOptions ++= Seq("-Xlint:unchecked", "-Xlint:deprecation")
21 | )
22 |
23 | object Dependencies {
24 |
25 | object V {
26 | val Scalaz = "6.0.4"
27 | val Camel = "2.9.2"
28 | val Akka = "1.3"
29 | val ActiveMQ = "5.6.0"
30 | val Slf4j = "1.6.4"
31 | val ScalaTest = "1.7.2"
32 | val Junit = "4.8.2"
33 | }
34 |
35 | lazy val scalazCore = "org.scalaz" %% "scalaz-core" % V.Scalaz
36 | lazy val camelCore = "org.apache.camel" % "camel-core" % V.Camel
37 | lazy val camelJms = "org.apache.camel" % "camel-jms" % V.Camel
38 | lazy val cameHttp = "org.apache.camel" % "camel-http" % V.Camel
39 | lazy val camelJetty = "org.apache.camel" % "camel-jetty" % V.Camel
40 | lazy val camelSpring = "org.apache.camel" % "camel-spring" % V.Camel
41 | lazy val akkaCamel = "se.scalablesolutions.akka" % "akka-camel" % V.Akka
42 |
43 | lazy val activemqCore = "org.apache.activemq" % "activemq-core" % V.ActiveMQ
44 | lazy val slf4jSimple = "org.slf4j" % "slf4j-simple" % V.Slf4j
45 | lazy val scalatest = "org.scalatest" %% "scalatest" % V.ScalaTest
46 | lazy val junit = "junit" % "junit" % V.Junit
47 | }
48 |
49 | lazy val root = Project("root", file(".")) aggregate(core, actor, samples)
50 |
51 | lazy val core = Project(
52 | id = "scalaz-camel-core",
53 | base = file("scalaz-camel-core"),
54 | settings = defaultSettings ++ Seq(libraryDependencies ++= Seq(
55 | Dependencies.scalazCore % "compile", Dependencies.camelCore % "compile",
56 | Dependencies.camelJms % "test", Dependencies.cameHttp % "test",
57 | Dependencies.camelJetty % "test", Dependencies.camelSpring % "test",
58 | Dependencies.activemqCore % "test", Dependencies.slf4jSimple % "test",
59 | Dependencies.scalatest % "test", Dependencies.junit % "test"))
60 | )
61 |
62 | lazy val actor = Project(
63 | id = "scalaz-camel-akka",
64 | base = file("scalaz-camel-akka"),
65 | dependencies = Seq(core),
66 | settings = defaultSettings ++ Seq(libraryDependencies ++= Seq(
67 | Dependencies.camelCore % "compile", Dependencies.akkaCamel,
68 | Dependencies.scalatest % "test", Dependencies.slf4jSimple % "test"))
69 | )
70 |
71 | lazy val samples = Project(
72 | id = "scalaz-camel-samples",
73 | base = file("scalaz-camel-samples"),
74 | dependencies = Seq(core),
75 | settings = defaultSettings ++ Seq(libraryDependencies ++= Seq(
76 | Dependencies.scalazCore % "compile", Dependencies.camelCore % "compile",
77 | Dependencies.camelJetty % "test", Dependencies.camelSpring % "compile",
78 | Dependencies.camelJms % "test", Dependencies.camelJms % "test",
79 | Dependencies.camelJetty % "test", Dependencies.camelSpring % "compile",
80 | Dependencies.activemqCore % "test", Dependencies.slf4jSimple % "test",
81 | Dependencies.scalatest % "test", Dependencies.junit % "test"))
82 | )
83 |
84 | }
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | resolvers += "sbt-idea-repo" at "http://mpeltonen.github.com/maven/"
2 |
3 | addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.0.0")
4 |
--------------------------------------------------------------------------------
/scalaz-camel-akka/src/main/scala/akka/actor/Sender.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2011 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package akka.actor
17 |
18 | import java.net.InetSocketAddress
19 | import java.util.concurrent.atomic.AtomicReference
20 |
21 | import akka.dispatch.{CompletableFuture, MessageDispatcher, MessageInvocation}
22 |
23 | import scalaz.camel.core.Conv._
24 | import scalaz.camel.core.Message
25 |
26 | /**
27 | * Sender used for communication with actors via !. Replies to this
28 | * sender are passed to continuation k.
29 | *
30 | * @author Martin Krasser
31 | */
32 | sealed class Sender(k: MessageValidation => Unit) extends ActorRef with ScalaActorRef {
33 | def start = { _status = akka.actor.ActorRefInternals.RUNNING; this }
34 | def stop = { _status = akka.actor.ActorRefInternals.SHUTDOWN }
35 |
36 | /**
37 | * Processes replies by passing it to continuation k. If the reply is of
38 | *
MessageValidation then it is passed to k as isMessage then it is converted to either Success or
41 | * Failure (depending on the the message.exception value)
42 | * before passing to kSuccess(Message(body)) that
44 | * is then passed to k33 | * val context: CamelContext = ... 34 | * implicit val router = new Router(context) with ActorMgnt 35 | *36 | * 37 | * @author Martin Krasser 38 | */ 39 | trait ActorMgnt { this: ContextMgnt => 40 | 41 | /** 42 | * Binds the life cycle of
actor to that of the current CamelContext.
43 | */
44 | def manage(actor: ActorRef): ActorRef = {
45 | context.addLifecycleStrategy(new LifecycleSync(actor))
46 | val service = context.asInstanceOf[ServiceSupport]
47 | if (service.isStarted) actor.start else actor
48 | }
49 | }
50 |
51 | /**
52 | * @author Martin Krasser
53 | */
54 | private[camel] class LifecycleSync(actor: ActorRef) extends LifecycleStrategy {
55 | import org.apache.camel.spi.RouteContext
56 | import org.apache.camel.builder.ErrorHandlerBuilder
57 |
58 | def onContextStart(context: CamelContext) = actor.start
59 | def onContextStop(context: CamelContext) = actor.stop
60 |
61 | // no action by default
62 | def onThreadPoolAdd(camelContext: CamelContext, threadPool: ThreadPoolExecutor) = {}
63 | def onErrorHandlerAdd(routeContext: RouteContext, errorHandler: Processor, errorHandlerBuilder: ErrorHandlerBuilder) = {}
64 | def onRouteContextCreate(routeContext: RouteContext) = {}
65 | def onRoutesRemove(routes: Collection[Route]) = {}
66 | def onRoutesAdd(routes: Collection[Route]) = {}
67 | def onServiceRemove(context: CamelContext, service: Service, route: Route) = {}
68 | def onServiceAdd(context: CamelContext, service: Service, route: Route) = {}
69 | def onEndpointRemove(endpoint: Endpoint) = {}
70 | def onEndpointAdd(endpoint: Endpoint) = {}
71 | def onComponentRemove(name: String, component: Component) = {}
72 | def onComponentAdd(name: String, component: Component) = {}
73 | def onThreadPoolAdd(context: CamelContext, executor: ThreadPoolExecutor, s: String, s1: String, s2: String, s3: String) {}
74 | def onErrorHandlerAdd(routeContext: RouteContext, errorHandler: Processor, errorHandlerBuilder: ErrorHandlerFactory) {}
75 | }
76 |
--------------------------------------------------------------------------------
/scalaz-camel-akka/src/main/scala/scalaz/camel/akka/ActorRefx.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2011 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package scalaz.camel.akka
17 |
18 | import akka.actor.ActorRef
19 |
20 | /**
21 | * ActorRef wrapper.
22 | *
23 | * @author Martin Krasser
24 | */
25 | class ActorRefx(actor: ActorRef) {
26 | /** Binds the life cycle of actor to that of the current CamelContext */
27 | def manage(implicit am: ActorMgnt): ActorRef = am.manage(actor)
28 |
29 | /** Returns the endpoint URI of actor */
30 | def uri: String = "actor:uuid:%s" format actor.uuid
31 |
32 | /** Returns the endpoint URI of actor containing the given options */
33 | def uri(options: String): String = "%s?%s" format (uri, options)
34 | }
35 |
--------------------------------------------------------------------------------
/scalaz-camel-akka/src/main/scala/scalaz/camel/akka/Akka.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2011 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package scalaz.camel.akka
17 |
18 | import akka.actor.ActorRef
19 |
20 | import scalaz.camel.core
21 |
22 | /**
23 | * Provides the Akka-specific Camel DSL.
24 | *
25 | * @author Martin Krasser
26 | */
27 | trait Akka extends DslEndpoint with DslEip with Conv with core.DslEndpoint with core.Conv {
28 | implicit def actorRefToActorRefx(actor: ActorRef) = new ActorRefx(actor)
29 | }
30 |
--------------------------------------------------------------------------------
/scalaz-camel-akka/src/main/scala/scalaz/camel/akka/Conv.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2011 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package scalaz.camel.akka
17 |
18 | import akka.actor.ActorRef
19 | import akka.actor.Sender
20 |
21 | import scalaz._
22 |
23 | import scalaz.camel.core.Conv._
24 | import scalaz.camel.core.Message
25 |
26 | /**
27 | * @author Martin Krasser
28 | */
29 | trait Conv {
30 | import Scalaz.success
31 |
32 | /**
33 | * Converts actor into a MessageProcessor. The created processor
34 | * supports multiple replies from actor.
35 | */
36 | def messageProcessor(actor: ActorRef): MessageProcessor =
37 | (m: Message, k: MessageValidation => Unit) => {
38 | if (m.context.oneway) {
39 | actor.!(m); k(success(m))
40 | } else {
41 | actor.!(m)(Some(new Sender(k).start))
42 | }
43 | }
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/scalaz-camel-akka/src/main/scala/scalaz/camel/akka/Dsl.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2011 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package scalaz.camel.akka
17 |
18 | import scalaz.camel.core
19 | import scalaz.camel.core.Conv._
20 | import scalaz.camel.core.Message
21 |
22 | import akka.actor.Actor
23 | import akka.actor.ActorRef
24 |
25 | /**
26 | * @author Martin Krasser
27 | */
28 | object DslEip {
29 | type AggregationFunction = (Message, Message) => Message
30 | type CompletionPredicate = (Message) => Boolean
31 | }
32 |
33 | /**
34 | * @author Martin Krasser
35 | */
36 | trait DslEip { this: DslEndpoint =>
37 | type AggregationFunction = DslEip.AggregationFunction
38 | type CompletionPredicate = DslEip.CompletionPredicate
39 |
40 | /**
41 | * Preliminary support for actor-based aggregator EIP.
42 | */
43 | def aggregate(implicit am: ActorMgnt) = AggregateDefinition(am)
44 |
45 | /**
46 | * Preliminary support for actor-based aggregator EIP.
47 | */
48 | case class AggregateDefinition(am: ActorMgnt, f: AggregationFunction = (m1, m2) => m2) {
49 | def using(f: AggregationFunction) = AggregateDefinition(am, f)
50 | def until(p: CompletionPredicate): MessageProcessor = to(manage(Actor.actorOf(new Aggregator(f, p)))(am))
51 | }
52 |
53 | private class Aggregator(f: AggregationFunction, p: CompletionPredicate) extends Actor {
54 | var current: Option[Message] = None
55 |
56 | def receive = {
57 | case message: Message => {
58 | val m = update(message)
59 | if (p(m)) self.reply(m)
60 | }
61 | }
62 |
63 | def update(message: Message): Message = {
64 | current match {
65 | case None => { current = Some(message) }
66 | case Some(m) => { current = Some(f(m, message))}
67 | }
68 | current.get
69 | }
70 | }
71 | }
72 |
73 | /**
74 | * @author Martin Krasser
75 | */
76 | trait DslEndpoint { this: core.DslEndpoint with Conv =>
77 | /**
78 | * Binds the life cycle of actor to that of the current CamelContext.
79 | */
80 | def manage(actor: ActorRef)(implicit am: ActorMgnt): ActorRef =
81 | am.manage(actor)
82 |
83 | /**
84 | * Creates a MessageProcessor for communicating with actor.
85 | * The created processor supports multiple replies from actor.
86 | */
87 | def to(actor: ActorRef): MessageProcessor =
88 | messageProcessor(actor)
89 | }
90 |
--------------------------------------------------------------------------------
/scalaz-camel-akka/src/test/resources/akka.conf:
--------------------------------------------------------------------------------
1 | akka{
2 | }
--------------------------------------------------------------------------------
/scalaz-camel-akka/src/test/resources/logback-test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Strategy.Sequential.
30 | */
31 | var dispatchConcurrencyStrategy: Strategy = Strategy.Sequential
32 |
33 | /**
34 | * Concurrency strategy to use for distributing messages to destinations with the
35 | * multicast and scatter-gather EIPs. Defaults to Strategy.Sequential.
36 | */
37 | var multicastConcurrencyStrategy: Strategy = Strategy.Sequential
38 |
39 | protected def dispatchStrategy = dispatchConcurrencyStrategy
40 | protected def multicastStrategy = multicastConcurrencyStrategy
41 |
42 | implicit def messageProcessorToMessageRoute(p: MessageProcessor): MessageRoute =
43 | messageRoute(p)
44 |
45 | implicit def messageProcessorToMessageRoute(p: Message => Message): MessageRoute =
46 | messageRoute(messageProcessor(p))
47 |
48 | implicit def camelProcessorToMessageRoute(p: Processor)(implicit cm: ContextMgnt): MessageRoute =
49 | messageRoute(messageProcessor(p, cm))
50 |
51 | implicit def responderToResponderApplication(r: Responder[MessageValidation]) =
52 | new ResponderApplication(r)
53 |
54 | implicit def routeToRouteApplication(p: MessageRoute) =
55 | new RouteApplication(p)
56 | }
--------------------------------------------------------------------------------
/scalaz-camel-core/src/main/scala/scalaz/camel/core/Conv.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2011 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package scalaz.camel.core
17 |
18 | import org.apache.camel.{AsyncCallback, AsyncProcessor, Exchange, Processor}
19 |
20 | import scalaz._
21 |
22 | /**
23 | * Provides converters for constructing message processing routes from
24 | *
25 | * MessageProcessor)Camel object.
34 | *
35 | * @author Martin Krasser
36 | */
37 | trait Conv {
38 | import scalaz.concurrent.Strategy
39 | import Scalaz._
40 | import Message._
41 |
42 | type MessageValidation = Conv.MessageValidation
43 | type MessageProcessor = Conv.MessageProcessor
44 | type MessageRoute = Conv.MessageRoute
45 |
46 | /**
47 | * Set the message context of m2 on m1 unless an context update should be skipped.
48 | */
49 | private val updateContext = (m1: Message) => (m2: Message) =>
50 | (if (!m1.skipContextUpdate) m1.setContextFrom(m2) else m1).setSkipContextUpdate(false)
51 |
52 | /**
53 | * Concurrency strategy for dispatching messages along the processor chain (i.e. route).
54 | */
55 | protected def dispatchStrategy: Strategy
56 |
57 | /**
58 | * Semigroup to 'append' failure messages. Returns the first failure message and ignores
59 | * the second. Needed for applicative usage of MessageValidation.
60 | */
61 | implicit def ExceptionSemigroup: Semigroup[Message] = semigroup((m1, m2) => m1)
62 |
63 | /**
64 | * A continuation monad for constructing routes from CPS message processors. It applies the
65 | * concurrency strategy returned by dispatchStrategy for dispatching messages
66 | * along the processor chain (i.e. route). Success messages are dispatched to the next processor
67 | * which in turn passes its result to continuation k. Failure messages are passed
68 | * directly to continuation k (by-passing the remaining processors in the chain).
69 | */
70 | class MessageValidationResponder(v: MessageValidation, p: MessageProcessor) extends Responder[MessageValidation] {
71 | def respond(k: MessageValidation => Unit) = v match {
72 | case Success(m) => dispatchStrategy.apply(p(m, r => k(v <*> r ∘ updateContext /* experimental */)))
73 | case Failure(m) => dispatchStrategy.apply(k(Failure(m)))
74 | }
75 | }
76 |
77 | /**
78 | * Creates a message processing route component from a CPS message processor
79 | */
80 | def messageRoute(p: MessageProcessor): MessageRoute =
81 | kleisli((v: MessageValidation) => new MessageValidationResponder(v, p).map(r => r))
82 |
83 | /**
84 | * Creates a CPS message processor from a message processing route
85 | */
86 | def messageProcessor(p: MessageRoute): MessageProcessor =
87 | (m: Message, k: MessageValidation => Unit) => p apply m.success respond k
88 |
89 | /**
90 | * Creates a CPS message processor from a direct-style message processor. The created
91 | * CPS processor executes the direct-style message processor synchronously.
92 | */
93 | def messageProcessor(p: Message => Message): MessageProcessor =
94 | messageProcessor(p, Strategy.Sequential)
95 |
96 | /**
97 | * Creates an CPS message processor from a direct-style message processor. The created
98 | * CPS processor executes the direct-style processor using the concurrency strategy
99 | * s.
100 | */
101 | def messageProcessor(p: Message => Message, s: Strategy): MessageProcessor = (m: Message, k: MessageValidation => Unit) =>
102 | s.apply { try { k(p(m).success) } catch { case e: Exception => k(m.setException(e).fail) } }
103 |
104 | /**
105 | * Creates a CPS message processor from a Camel message producer obtained from an endpoint
106 | * defined by URI. This method registers the created producer at the Camel context for
107 | * lifecycle management.
108 | */
109 | def messageProcessor(uri: String, em: EndpointMgnt, cm: ContextMgnt): MessageProcessor =
110 | messageProcessor(em.createProducer(uri), cm)
111 |
112 | /**
113 | * Creates a CPS message processor from a (synchronous or asynchronous) Camel processor.
114 | */
115 | def messageProcessor(p: Processor, cm: ContextMgnt): MessageProcessor =
116 | if (p.isInstanceOf[AsyncProcessor]) messageProcessor(p.asInstanceOf[AsyncProcessor], cm)
117 | else messageProcessor(new ProcessorAdapter(p), cm)
118 |
119 | /**
120 | * Creates a CPS message processor from an asynchronous Camel processor.
121 | */
122 | def messageProcessor(p: AsyncProcessor, cm: ContextMgnt): MessageProcessor = (m: Message, k: MessageValidation => Unit) => {
123 | import org.apache.camel.impl.DefaultExchange
124 |
125 | val me = new DefaultExchange(cm.context)
126 |
127 | me.getIn.fromMessage(m)
128 |
129 | p.process(me, new AsyncCallback {
130 | def done(doneSync: Boolean) =
131 | if (me.isFailed)
132 | k(resultMessage(me).fail)
133 | else
134 | k(resultMessage(me).success)
135 |
136 | private def resultMessage(me: Exchange) = {
137 | val rm = if (me.hasOut) me.getOut else me.getIn
138 | me.setOut(null)
139 | me.setIn(rm)
140 | rm.toMessage
141 | }
142 | })
143 | }
144 |
145 | /**
146 | * An AsyncProcessor interface for a (synchronous) Camel Processor.
147 | */
148 | private class ProcessorAdapter(p: Processor) extends AsyncProcessor {
149 | def process(exchange: Exchange) = throw new UnsupportedOperationException()
150 | def process(exchange: Exchange, callback: AsyncCallback) = {
151 | try {
152 | p.process(exchange)
153 | } catch {
154 | case e: Exception => exchange.setException(e)
155 | }
156 | callback.done(true)
157 | true
158 | }
159 | }
160 | }
161 |
162 | object Conv {
163 | /**
164 | * Type of a failed or successful response message. Will be replaced by
165 | * Either[Message, Message] with scalaz versions greater than 5.0.
166 | */
167 | type MessageValidation = Validation[Message, Message]
168 |
169 | /**
170 | * Type of a (potentially asynchronous) message processor that passes a message validation
171 | * result to a continuation of type MessageValidation => Unit. A CPS message
172 | * processor.
173 | */
174 | type MessageProcessor = (Message, MessageValidation => Unit) => Unit
175 |
176 | /**
177 | * Type of a message processing route or a single route component. These can be composed
178 | * via Kleisli composition.
179 | */
180 | type MessageRoute = Kleisli[Responder, MessageValidation, MessageValidation]
181 | }
--------------------------------------------------------------------------------
/scalaz-camel-core/src/main/scala/scalaz/camel/core/Dsl.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2011 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package scalaz.camel.core
17 |
18 | import java.util.concurrent.{BlockingQueue, CountDownLatch, TimeUnit}
19 | import java.util.concurrent.atomic.AtomicInteger
20 |
21 | import org.apache.camel.{Exchange, AsyncCallback, AsyncProcessor}
22 |
23 | import scalaz._
24 | import Scalaz._
25 | import Message._
26 |
27 | import concurrent.Promise
28 | import concurrent.Strategy
29 |
30 | /**
31 | * Message processors representing enterprise integration patterns (EIPs).
32 | *
33 | * @author Martin Krasser
34 | */
35 | trait DslEip { this: Conv =>
36 |
37 | /**
38 | * Name of the position message header needed by scatter-gather. Needed to
39 | * preserve the order of messages that are distributed to destinations.
40 | */
41 | val Position = "scalaz.camel.multicast.position"
42 |
43 | /**
44 | * Concurrency strategy for distributing messages to destinations
45 | * with the multicast and scatter-gather EIPs.
46 | */
47 | protected def multicastStrategy: Strategy
48 |
49 | /**
50 | * Creates a message processor that sets the message context's oneway field to true.
51 | */
52 | def oneway: MessageProcessor = oneway(true)
53 |
54 | /**
55 | * Creates a message processor that sets the message context's oneway field to given value.
56 | */
57 | def oneway(oneway: Boolean): MessageProcessor = messageProcessor { m: Message => m.setOneway(oneway) }
58 |
59 | /**
60 | * Creates a message processor that routes messages based on pattern matching. Implements
61 | * the content-based router EIP.
62 | */
63 | def choose(f: PartialFunction[Message, MessageRoute]): MessageProcessor =
64 | (m: Message, k: MessageValidation => Unit) => {
65 | f.lift(m) match {
66 | case Some(r) => messageProcessor(r)(m, k)
67 | case None => k(m.success)
68 | }
69 | }
70 |
71 | /**
72 | * Creates a message processor that distributes messages to given destinations. The created
73 | * processor applies the concurrency strategy returned by multicastStrategy
74 | * to distribute messages. Distributed messages are not combined, instead n responses
75 | * are sent where n is the number of destinations. Implements the static recipient-list EIP.
76 | */
77 | def multicast(destinations: MessageRoute*): MessageProcessor =
78 | (m: Message, k: MessageValidation => Unit) => {
79 | 0 until destinations.size foreach { i =>
80 | multicastStrategy.apply {
81 | destinations(i) apply m.success respond { mv => k(mv ∘ (_.addHeader(Position -> i))) }
82 | }
83 | }
84 | }
85 |
86 | /**
87 | * Creates a message processor that generates a sequence of messages using f and
88 | * sends n responses taken from the generated message sequence. Implements the splitter EIP.
89 | */
90 | def split(f: Message => Seq[Message]): MessageProcessor =
91 | (m: Message, k: MessageValidation => Unit) => {
92 | try {
93 | f(m) foreach { r => k(r.success) }
94 | } catch {
95 | case e: Exception => k(m.setException(e).fail)
96 | }
97 | }
98 |
99 | /**
100 | * Creates a message processor that filters messages if f returns None and sends
101 | * a response if f returns Some message. Allows providers of f to
102 | * aggregate messages and continue processing with a combined message, for example.
103 | * Implements the aggregator EIP.
104 | */
105 | def aggregate(f: Message => Option[Message]): MessageProcessor =
106 | (m: Message, k: MessageValidation => Unit) => {
107 | try {
108 | f(m) match {
109 | case Some(r) => k(r.success)
110 | case None => { /* do not continue */ }
111 | }
112 | } catch {
113 | case e: Exception => k(m.setException(e).fail)
114 | }
115 | }
116 |
117 | /**
118 | * Creates a message processor that filters messages by evaluating predicate p. If
119 | * p evaluates to true a response is sent, otherwise the message is
120 | * filtered. Implements the filter EIP.
121 | */
122 | def filter(p: Message => Boolean) = aggregate { m: Message =>
123 | if (p(m)) Some(m) else None
124 | }
125 |
126 | /**
127 | * Creates a builder for a scatter-gather processor. Implements the scatter-gather EIP.
128 | *
129 | * @see ScatterDefinition
130 | */
131 | def scatter(destinations: MessageRoute*) = new ScatterDefinition(destinations: _*)
132 |
133 | /**
134 | * Builder for a scatter-gather processor.
135 | *
136 | * @see scatter
137 | */
138 | class ScatterDefinition(destinations: MessageRoute*) {
139 |
140 | /**
141 | * Creates a message processor that scatters messages to destinations and
142 | * gathers and combines them using combine. Messages are scattered to
143 | * destinations using the concurrency strategy returned by
144 | * multicastStrategy. Implements the scatter-gather EIP.
145 | *
146 | * @see scatter
147 | */
148 | def gather(combine: (Message, Message) => Message): MessageRoute = {
149 | val mcp = multicastProcessor(destinations.toList, combine)
150 | messageRoute((m: Message, k: MessageValidation => Unit) => mcp(m, k))
151 | }
152 | }
153 |
154 | /**
155 | * Creates a message processor that sets exception e on the input message and
156 | * generates a failure.
157 | */
158 | def failWith(e: Exception): MessageProcessor =
159 | (m: Message, k: MessageValidation => Unit) => k(m.setException(e).fail)
160 |
161 | // ------------------------------------------
162 | // Internal
163 | // ------------------------------------------
164 |
165 | /**
166 | * Creates a message processor that distributes messages to destinations (using multicast) and gathers
167 | * and combines the responses using an aggregator with gatherFunction.
168 | */
169 | private def multicastProcessor(destinations: List[MessageRoute], combine: (Message, Message) => Message): MessageProcessor = {
170 | (m: Message, k: MessageValidation => Unit) => {
171 | val sgm = multicast(destinations: _*)
172 | val sga = aggregate(gatherFunction(combine, destinations.size))
173 | messageRoute(sgm) >=> messageRoute(sga) apply m.success respond k
174 | }
175 | }
176 |
177 | /**
178 | * Creates an aggregation function that gathers and combines multicast responses.
179 | */
180 | private def gatherFunction(combine: (Message, Message) => Message, count: Int): Message => Option[Message] = {
181 | val ct = new AtomicInteger(count)
182 | val ma = Array.fill[Message](count)(null)
183 | (m: Message) => {
184 | for (pos <- m.header(Position).asInstanceOf[Option[Int]]) {
185 | ma.synchronized(ma(pos) = m)
186 | }
187 | if (ct.decrementAndGet == 0) {
188 | val ml = ma.synchronized(ma.toList)
189 | Some(ml.tail.foldLeft(ml.head)((m1, m2) => combine(m1, m2).removeHeader(Position)))
190 | } else {
191 | None
192 | }
193 | }
194 | }
195 | }
196 |
197 | trait DslAttempt { this: Conv =>
198 |
199 | type AttemptHandler1 = PartialFunction[Exception, MessageRoute]
200 | type AttemptHandlerN = PartialFunction[(Exception, RetryState), MessageRoute]
201 |
202 | /**
203 | * Captures the state of (repeated) message routing attempts. A retry state is defined by
204 | * rhorig used as input for the attempted routes.
217 | */
218 | def orig(s: RetryState): MessageProcessor =
219 | (m: Message, k: MessageValidation => Unit) => k(s.orig.success)
220 |
221 | /**
222 | * Creates a builder for an attempt-fallback processor. The processor makes a single attempt
223 | * to apply route r to an input message.
224 | */
225 | def attempt(r: MessageRoute) = new AttemptDefinition0(r)
226 |
227 | /**
228 | * Creates a builder for an attempt(n)-fallback processor. The processor can be used to make n
229 | * attempts to apply route r to an input message.
230 | *
231 | * @see orig
232 | * @see retry
233 | */
234 | def attempt(count: Int)(r: MessageRoute) = new AttemptDefinitionN(r, count - 1)
235 |
236 | /**
237 | * Creates a message processor that makes an additional attempt to apply s.r
238 | * (the initially attempted route) to its input message. The message processor decreases
239 | * s.count (the retry count by one). A retry attempt is only made if an exception
240 | * is set on the message an s.h (a retry handler) is defined at that exception.
241 | */
242 | def retry(s: RetryState): MessageProcessor =
243 | (m: Message, k: MessageValidation => Unit) => {
244 | s.r apply m.success respond { mv => mv match {
245 | case Success(_) => k(mv)
246 | case Failure(m) => {
247 | for {
248 | e <- m.exception
249 | r <- s.h.lift(e, s.next)
250 | } {
251 | if (s.count > 0) r apply m.exceptionHandled.success respond k else k(mv)
252 | }
253 | }
254 | }}
255 |
256 | }
257 |
258 | /**
259 | * Builder for an attempt-retry processor.
260 | */
261 | class AttemptDefinition0(r: MessageRoute) {
262 | /**
263 | * Creates an attempt-retry processor using retry handlers defined by h.
264 | */
265 | def fallback(h: AttemptHandler1): MessageProcessor =
266 | (m: Message, k: MessageValidation => Unit) => {
267 | r apply m.success respond { mv => mv match {
268 | case Success(_) => k(mv)
269 | case Failure(m) => {
270 | for {
271 | e <- m.exception
272 | r <- h.lift(e)
273 | } {
274 | r apply m.exceptionHandled.success respond k
275 | }
276 | }
277 | }}
278 | }
279 | }
280 |
281 | /**
282 | * Builder for an attempt(n)-retry processor.
283 | */
284 | class AttemptDefinitionN(r: MessageRoute, count: Int) {
285 | /**
286 | * Creates an attempt(n)-retry processor using retry handlers defined by h.
287 | */
288 | def fallback(h: AttemptHandlerN): MessageProcessor =
289 | (m: Message, k: MessageValidation => Unit) => {
290 | retry(new RetryState(r, h, count, m))(m, k)
291 | }
292 | }
293 | }
294 |
295 | /**
296 | * DSL for endpoint management.
297 | *
298 | * @author Martin Krasser
299 | */
300 | trait DslEndpoint { this: Conv =>
301 |
302 | /**
303 | * Creates a consumer for an endpoint represented by uri and connects it to the route
304 | * r. This method registers the created consumer at the Camel context for lifecycle
305 | * management.
306 | */
307 | def from(uri: String)(r: MessageRoute)(implicit em: EndpointMgnt, cm: ContextMgnt): Unit =
308 | em.createConsumer(uri, new RouteProcessor(r))
309 |
310 | /**
311 | * Creates a CPS processor that acts as a producer to the endpoint represented by uri.
312 | * This method registers the created producer at the Camel context for lifecycle management.
313 | */
314 | def to(uri: String)(implicit em: EndpointMgnt, cm: ContextMgnt): MessageProcessor = messageProcessor(uri, em, cm)
315 |
316 | private class RouteProcessor(val p: MessageRoute) extends AsyncProcessor {
317 | import RouteProcessor._
318 |
319 | /**
320 | * Synchronous message processing.
321 | */
322 | def process(exchange: Exchange) = {
323 | val latch = new CountDownLatch(1)
324 | process(exchange, new AsyncCallback() {
325 | def done(doneSync: Boolean) = {
326 | latch.countDown
327 | }
328 | })
329 | latch.await
330 | }
331 |
332 | /**
333 | * Asynchronous message processing (may be synchronous as well if all message processor are synchronous
334 | * processors and all concurrency strategies are configured to be Sequential).
335 | */
336 | def process(exchange: Exchange, callback: AsyncCallback) =
337 | if (exchange.getPattern.isOutCapable) processInOut(exchange, callback) else processInOnly(exchange, callback)
338 |
339 | private def processInOut(exchange: Exchange, callback: AsyncCallback) = {
340 | route(exchange.getIn.toMessage, once(respondTo(exchange, callback)))
341 | false
342 | }
343 |
344 | private def processInOnly(exchange: Exchange, callback: AsyncCallback) = {
345 | route(exchange.getIn.toMessage.setOneway(true), ((mv: MessageValidation) => { /* ignore any result */ }))
346 | callback.done(true)
347 | true
348 | }
349 |
350 | private def route(message: Message, k: MessageValidation => Unit): Unit = p apply message.success respond k
351 | }
352 |
353 | private object RouteProcessor {
354 | def respondTo(exchange: Exchange, callback: AsyncCallback): MessageValidation => Unit = (mv: MessageValidation ) => mv match {
355 | case Success(m) => respond(m, exchange, callback)
356 | case Failure(m) => respond(m, exchange, callback)
357 | }
358 |
359 | def respond(message: Message, exchange: Exchange, callback: AsyncCallback): Unit = {
360 | message.exception ∘ (exchange.setException(_))
361 | exchange.getIn.fromMessage(message)
362 | exchange.getOut.fromMessage(message)
363 | callback.done(false)
364 | }
365 |
366 | def once(k: MessageValidation => Unit): MessageValidation => Unit = {
367 | val done = new java.util.concurrent.atomic.AtomicBoolean(false)
368 | (mv: MessageValidation) => if (!done.getAndSet(true)) k(mv)
369 | }
370 | }
371 | }
372 |
373 | /**
374 | * DSL support classes for applying message validation responders and message processing routes.
375 | *
376 | * @see Camel.responderToResponderApplication
377 | * @see Camel.routeToRouteApplication
378 | *
379 | * @author Martin Krasser
380 | */
381 | trait DslApply { this: Conv =>
382 |
383 | /**
384 | * Applies a message validation responder r.
385 | *
386 | * @see Camel.responderToResponderApplication
387 | */
388 | class ResponderApplication(r: Responder[MessageValidation]) {
389 | /** Apply responder r and wait for response */
390 | def response: MessageValidation = responseQueue.take
391 |
392 | /** Apply responder r and wait for response with timeout */
393 | def response(timeout: Long, unit: TimeUnit): MessageValidation = responseQueue.poll(timeout, unit)
394 |
395 | /** Apply responder r and get response promise */
396 | def responsePromise(implicit s: Strategy): Promise[MessageValidation] = promise(responseQueue.take)
397 |
398 | /** Apply responder r and get response queue */
399 | def responseQueue: BlockingQueue[MessageValidation] = {
400 | val queue = new java.util.concurrent.LinkedBlockingQueue[MessageValidation](10)
401 | r respond { mv => queue.put(mv) }
402 | queue
403 | }
404 | }
405 |
406 | /**
407 | * Applies a message processing route r.
408 | *
409 | * @see Camel.routeToRouteApplication
410 | */
411 | class RouteApplication(r: MessageRoute) {
412 | /** Apply route r to message m and wait for response */
413 | def process(m: Message) =
414 | new ResponderApplication(r apply m.success).response
415 |
416 | /** Apply route r to message m and wait for response with timeout */
417 | def process(m: Message, timeout: Long, unit: TimeUnit) =
418 | new ResponderApplication(r apply m.success).response(timeout: Long, unit: TimeUnit)
419 |
420 | /** Apply route r to message m and get response promise */
421 | def submit(m: Message)(implicit s: Strategy) =
422 | new ResponderApplication(r apply m.success).responsePromise
423 |
424 | /** Apply route r to message m and get response queue */
425 | def submitN(m: Message) =
426 | new ResponderApplication(r apply m.success).responseQueue
427 |
428 | /** Apply route r to messages ms and wait for (first) response */
429 | def process(ms: Seq[Message]) =
430 | submitN(ms).take
431 |
432 | /** Apply route r to messages ms and wait for (first) response with timeout */
433 | def process(ms: Seq[Message], timeout: Long, unit: TimeUnit) =
434 | submitN(ms).poll(timeout, unit)
435 |
436 | /** Apply route r to messaged md and get response promise */
437 | def submit(ms: Seq[Message])(implicit s: Strategy) =
438 | promise(submitN(ms).take)
439 |
440 | /** Apply route r to messaged md and get response queue */
441 | def submitN(ms: Seq[Message]) = {
442 | val queue = new java.util.concurrent.LinkedBlockingQueue[MessageValidation]
443 | for (m <- ms) r apply m.success respond { mv => queue.put(mv) }
444 | queue
445 | }
446 | }
447 | }
448 |
--------------------------------------------------------------------------------
/scalaz-camel-core/src/main/scala/scalaz/camel/core/Message.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2011 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package scalaz.camel.core
17 |
18 | import org.apache.camel.{Exchange, ExchangePattern, CamelContext, Message => CamelMessage}
19 |
20 | /**
21 | * An immutable representation of a Camel message.
22 | *
23 | * @author Martin Krasser
24 | */
25 | case class Message(body: Any, headers: Map[String, Any] = Map.empty) {
26 |
27 | private val SkipContextUpdate = "scalaz.camel.update.context.skip"
28 |
29 | // TODO: consider making Message a parameterized type
30 | // TODO: consider making Message an instance of Functor
31 |
32 | val context = MessageContext()
33 |
34 | override def toString = "Message: %s" format body
35 |
36 | def setBody(body: Any) = Message(body, headers, context)
37 |
38 | def setHeaders(headers: Map[String, Any]) = Message(body, headers, context)
39 |
40 | def addHeaders(headers: Map[String, Any]) = Message(body, this.headers ++ headers, context)
41 |
42 | def addHeader(header: (String, Any)) = Message(body, headers + header, context)
43 |
44 | def removeHeader(headerName: String) = Message(body, headers - headerName, context)
45 |
46 | def setContext(context: MessageContext) = Message(body, headers, context).setSkipContextUpdate(true)
47 |
48 | def setContextFrom(m: Message) = setContext(m.context)
49 |
50 | def setOneway(oneway: Boolean) = setContext(context.setOneway(oneway))
51 |
52 | def setException(e: Exception) = setContext(context.setException(Some(e)))
53 |
54 | def exception: Option[Exception] = context.exception
55 |
56 | def headers(names: Set[String]): Map[String, Any] = headers.filter(names contains _._1)
57 |
58 | def header(name: String): Option[Any] = headers.get(name)
59 |
60 | def headerAs[A](name: String)(implicit m: Manifest[A], mgnt: ContextMgnt): Option[A] =
61 | header(name).map(convertTo[A](m.erasure.asInstanceOf[Class[A]], mgnt.context) _)
62 |
63 | def bodyAs[A](implicit m: Manifest[A], mgnt: ContextMgnt): A =
64 | convertTo[A](m.erasure.asInstanceOf[Class[A]], mgnt.context)(body)
65 |
66 | // TODO: remove once Message is a Functor
67 | def bodyTo[A](implicit m: Manifest[A], mgnt: ContextMgnt): Message =
68 | Message(convertTo[A](m.erasure.asInstanceOf[Class[A]], mgnt.context)(body), headers, context)
69 |
70 | // TODO: remove once Message is a Functor
71 | def transform[A](transformer: A => Any)(implicit m: Manifest[A], mgnt: ContextMgnt) =
72 | setBody(transformer(bodyAs[A]))
73 |
74 | // TODO: remove once Message is a Functor
75 | def appendToBody(body: Any)(implicit mgnt: ContextMgnt) =
76 | setBody(bodyAs[String] + convertTo[String](classOf[String], mgnt.context)(body))
77 |
78 | private[camel] def exceptionHandled =
79 | Message(body, headers, context.setException(None))
80 |
81 | // Experimental
82 | private[camel] def setSkipContextUpdate(skip: Boolean) =
83 | if (skip) addHeader(SkipContextUpdate, skip) else removeHeader(SkipContextUpdate)
84 |
85 | // Experimental
86 | private[camel] def skipContextUpdate = header(SkipContextUpdate) match {
87 | case Some(v) => v.asInstanceOf[Boolean]
88 | case None => false
89 | }
90 |
91 | private def convertTo[A](c: Class[A], context: CamelContext)(a: Any): A =
92 | context.getTypeConverter.mandatoryConvertTo[A](c, a)
93 | }
94 |
95 | /**
96 | * An immutable representation of a Camel Exchange.
97 | *
98 | * @author Martin Krasser
99 | */
100 | case class MessageContext(oneway: Boolean, exception: Option[Exception]) {
101 | def setOneway(o: Boolean) = MessageContext(o, exception)
102 |
103 | def setException(e: Option[Exception]) = MessageContext(oneway, e)
104 | }
105 |
106 | /**
107 | * @author Martin Krasser
108 | */
109 | object Message {
110 | /** Creates a MessageConverter from a Camel message */
111 | implicit def camelMessageToConverter(cm: CamelMessage): MessageConverter = new MessageConverter(cm)
112 |
113 | /** Create a Message with body, headers and a message context */
114 | def apply(body: Any, headers: Map[String, Any], ctx: MessageContext): Message = new Message(body, headers) {
115 | override val context = ctx
116 | }
117 | }
118 |
119 | /**
120 | * @author Martin Krasser
121 | */
122 | object MessageContext {
123 | /** Creates a MessageContextConverter from a Camel exchange */
124 | implicit def camelExchangeToConverter(ce: Exchange) = new MessageContextConverter(ce)
125 |
126 | /** Create a default MessageContext with oneway set to false and no exception */
127 | def apply(): MessageContext = MessageContext(false, None)
128 | }
129 |
130 | /**
131 | * Converts between scalaz.camel.Message and org.apache.camel.Message.
132 | *
133 | * @author Martin Krasser
134 | */
135 | class MessageConverter(val cm: CamelMessage) {
136 | import scala.collection.JavaConversions._
137 | import MessageContext._
138 |
139 | def fromMessage(m: Message): CamelMessage = {
140 | cm.getExchange.fromMessageContext(m.context)
141 | cm.setBody(m.body)
142 | for (h <- m.headers) cm.getHeaders.put(h._1, h._2.asInstanceOf[AnyRef])
143 | cm
144 | }
145 |
146 | def toMessage: Message = toMessage(Map.empty)
147 | def toMessage(headers: Map[String, Any]): Message =
148 | Message(cm.getBody, cmHeaders(cm, headers), cm.getExchange.toMessageContext)
149 |
150 | private def cmHeaders(cm: CamelMessage, headers: Map[String, Any]) = headers ++ cm.getHeaders
151 | }
152 |
153 | /**
154 | * Converts between scalaz.camel.MessageContext and org.apache.camel.Exchange.
155 | *
156 | * @author Martin Krasser
157 | */
158 | class MessageContextConverter(val ce: Exchange) {
159 | def fromMessageContext(me: MessageContext) = {
160 | ce.setPattern(if (me.oneway) ExchangePattern.InOnly else ExchangePattern.InOut)
161 | ce.setException(me.exception match {
162 | case Some(e) => e
163 | case None => null
164 | })
165 | }
166 |
167 | def toMessageContext = MessageContext(
168 | if (ce.getPattern.isOutCapable) false else true,
169 | if (ce.getException == null) None else Some(ce.getException)
170 | )
171 | }
--------------------------------------------------------------------------------
/scalaz-camel-core/src/main/scala/scalaz/camel/core/Router.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2011 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package scalaz.camel.core
17 |
18 | import java.util.Collection
19 | import java.util.concurrent.ThreadPoolExecutor
20 |
21 | import org.apache.camel._
22 | import org.apache.camel.support.ServiceSupport
23 | import org.apache.camel.spi.LifecycleStrategy
24 | import java.lang.String
25 |
26 | /**
27 | * Registers created endpoint consumers and producers at the CamelContext for
28 | * lifecycle management.
29 | *
30 | * @author Martin Krasser
31 | */
32 | trait EndpointMgnt { this: ContextMgnt =>
33 | /**
34 | * Creates a consumer for endpoint defined by uri. The consumer is
35 | * registered at the CamelContext for lifecycle management. This method can be
36 | * used in context of both, started and not-yet-started CamelContext instances.
37 | *
38 | * @param uri endpoint URI.
39 | * @param p processor that is connected to the created endpoint consumer.
40 | */
41 | def createConsumer(uri: String, p: Processor): Consumer = {
42 | val service = context.asInstanceOf[ServiceSupport]
43 | val endpoint = context.getEndpoint(uri)
44 | val consumer = endpoint.createConsumer(p)
45 |
46 | context.addLifecycleStrategy(new LifecycleSync(consumer))
47 |
48 | if (service.isStarted) {
49 | endpoint.start // needed?
50 | consumer.start
51 | }
52 |
53 | consumer
54 | }
55 |
56 | /**
57 | * Creates a producer for endpoint defined by uri. The producer is
58 | * registered at the CamelContext for lifecycle management. This method can be
59 | * used used in context of both, started and not-yet-started CamelContext instances.
60 | *
61 | * @param uri endpoint URI.
62 | */
63 | def createProducer(uri: String): Producer = {
64 | val service = context.asInstanceOf[ServiceSupport]
65 | val endpoint = context.getEndpoint(uri)
66 | val producer = endpoint.createProducer
67 |
68 | context.addLifecycleStrategy(new LifecycleSync(producer))
69 |
70 | if (service.isStarted) {
71 | producer.start
72 | }
73 |
74 | producer
75 | }
76 | }
77 |
78 | /**
79 | * Manages a CamelContext
80 | *
81 | * @author Martin Krasser
82 | */
83 | trait ContextMgnt {
84 | /** Managed CamelContext */
85 | val context: CamelContext
86 |
87 | /** Starts the context */
88 | def start = context.start
89 | /** Stops the context */
90 | def stop = context.stop
91 | }
92 |
93 | /**
94 | * The Camel context and endpoint manager (implicitly) needed in context of routes.
95 | *
96 | * @author Martin Krasser
97 | */
98 | class Router(val context: CamelContext) extends EndpointMgnt with ContextMgnt {
99 | import org.apache.camel.component.direct.DirectComponent
100 |
101 | context.addComponent("direct", new DirectNostop)
102 |
103 | private class DirectNostop extends DirectComponent {
104 | override def doStop {}
105 | }
106 | }
107 |
108 | /**
109 | * @author Martin Krasser
110 | */
111 | private[camel] class LifecycleSync(service: Service) extends LifecycleStrategy {
112 | import org.apache.camel.spi.RouteContext
113 | import org.apache.camel.builder.ErrorHandlerBuilder
114 |
115 | def onContextStart(context: CamelContext) = service.start
116 | def onContextStop(context: CamelContext) = service.stop
117 |
118 | // no action by default
119 | def onThreadPoolAdd(camelContext: CamelContext, threadPool: ThreadPoolExecutor) = {}
120 | def onErrorHandlerAdd(routeContext: RouteContext, errorHandler: Processor, errorHandlerBuilder: ErrorHandlerBuilder) = {}
121 | def onRouteContextCreate(routeContext: RouteContext) = {}
122 | def onRoutesRemove(routes: Collection[Route]) = {}
123 | def onRoutesAdd(routes: Collection[Route]) = {}
124 | def onServiceRemove(context: CamelContext, service: Service, route: Route) = {}
125 | def onServiceAdd(context: CamelContext, service: Service, route: Route) = {}
126 | def onEndpointRemove(endpoint: Endpoint) = {}
127 | def onEndpointAdd(endpoint: Endpoint) = {}
128 | def onComponentRemove(name: String, component: Component) = {}
129 | def onComponentAdd(name: String, component: Component) = {}
130 | def onThreadPoolAdd(context: CamelContext, executor: ThreadPoolExecutor, s: String, s1: String, s2: String, s3: String) {}
131 |
132 | def onErrorHandlerAdd(routeContext: RouteContext, errorHandler: Processor, errorHandlerBuilder: ErrorHandlerFactory) {}
133 | }
134 |
--------------------------------------------------------------------------------
/scalaz-camel-core/src/test/resources/context.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |