tag:blogger.com,1999:blog-90747622934467160092024-03-06T04:35:54.211+00:00Tomek Lipski's blogImpressions and tips on Open Source, JVM, Clojure, Vaadin and Liferay.TLhttp://www.blogger.com/profile/02471354092762652778noreply@blogger.comBlogger13125tag:blogger.com,1999:blog-9074762293446716009.post-17801360951071931842014-05-19T15:00:00.000+01:002014-05-19T15:00:01.147+01:00Building Web Applications with Clojure - screencast<h3 style="text-align: justify;">
It's out!</h3>
<div style="text-align: left;">
I am pleased to announce, that my screencast: <a href="http://www.packtpub.com/building-web-applications-with-clojure/video">Building Web Applications with Clojure</a> has been released by Packt Publishing. It is an excellent resource to get you started with developing Clojure web applications or just to refresh existing knowledge if you got your feet wet already, but still feel like some general understanding is needed.</div>
<h3 style="text-align: left;">
Example codes & sample video</h3>
<div style="text-align: left;">
There a GitHub repo with example codes - where each video has a separate tag: <a href="https://github.com/tlipski/clojure-webapps">https://github.com/tlipski/clojure-webapps</a> and a sample: <a href="https://www.youtube.com/watch?v=_h1eQAgdFi4">https://www.youtube.com/watch?v=_h1eQAgdFi4</a>.</div>
<h3 style="text-align: left;">
Screencast contents</h3>
<div style="text-align: left;">
As there is a number of web frameworks/approaches in Clojure and usually it is up to a programmer to combine them into one working application, this course introduces viewers to basic mechanisms and techniques used when developing web applications with Clojure - such as Ring handlers, requests, responses and middleware.</div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
Next, we focus on interaction techniques: form/session/cookie handling, HTML generation and simple RESTful approach is covered.</div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
Then, we learn how to access external resources, such as PostgreSQL and MongoDB. After that, ClojureScript is briefly covered and finally some popular web libraries: Compojure, Liberator, Hiccup and Enlive are covered.</div>
<br />
<br />TLhttp://www.blogger.com/profile/02471354092762652778noreply@blogger.com1tag:blogger.com,1999:blog-9074762293446716009.post-89085481783275163392013-10-21T15:30:00.000+01:002013-10-21T15:32:38.019+01:00Ganelon tutorial, part 2: widgets and actions.<b>This part of Ganelon tutorial shows how to connect Clojure project to MongoDB and then create dynamic widgets and actions that manage application data. You can see the complete application for this step in a GitHub repository: <a href="https://github.com/tlipski/ganelon-tutorial/tree/PART2_WIDGETS">https://github.com/tlipski/ganelon-tutorial/tree/PART2_WIDGETS</a>.</b><br />
<br />
<i>If you want to see the final result of an entire tutorial, you can view the tutorial app running live at <a href="http://ganelon-tutorial.tomeklipski.com/">http://ganelon-tutorial.tomeklipski.com</a> or you can check out/star/fork the source code at <a href="http://github.com/tlipski/ganelon-tutorial">http://github.com/tlipski/ganelon-tutorial</a>.</i><br />
<i><br /></i>
For more information regarding Ganelon, you can visit the project website: <a href="http://ganelon.tomeklipski.com/">http://ganelon.tomeklipski.com</a>.<br />
<br />
<h2>Connecting to MongoDB</h2>
<p>Using mongodb is Clojure project is very easy and there's nothing Ganelon-specific about it.</p>
<p>First we will add dependency for <a href="https://github.com/aboekhoff/congomongo">congomongo</a> library (<code class="prettyprint lang-clj">[congomongo "0.4.1"]</code>) to <code class="prettyprint lang-clj">project.clj</code>:</p>
<pre class="prettyprint lang-clj">
:dependencies [[ganelon "0.9.0"]
<b>[congomongo "0.4.1"]</b>
[crypto-random "1.1.0"]]
</pre>
<p>There's also another new library referenced - <code class="prettyprint lang-clj">crypto-random</code>. It is a useful tool when it comes to generating secure random identifiers - something that will come in very handy when creating new meetups or invitations.</p>
<p>Having the MongoDB library in place, we can add connection setup to <a href="https://github.com/tlipski/ganelon-tutorial/blob/PART2_WIDGETS/src/ganelon/tutorial.clj">src/ganelon/tutorial.clj</a>:</p>
<pre class="prettyprint lang-clj">
(ns ganelon.tutorial
(:gen-class)
(:require [ganelon.tutorial.pages.routes]
[ring.middleware.stacktrace]
[ring.middleware.reload]
[ganelon.web.middleware :as middleware]
[ganelon.web.app :as webapp]
[noir.session :as sess]
<b>[somnium.congomongo :as db]</b>))
<b>(defn get-mongo-url []
(or
(get (System/getenv) "MONGOHQ_URL")
(System/getProperty "MONGOHQ_URL")
"mongodb://localhost/meetups"))</b>
(defn initialize[]
<b>(db/set-connection! (db/make-connection (get-mongo-url)))</b>)
</pre>
<p>The <code class="prettyprint lang-clj">get-mongo-url</code> function returns mongo db url either from environment or from default development setup. In real-life scenario, the connection options will be much more complex, including username&password, failover options, etc.</p>
<p>It is also noteworthy, that we don't have to manage MongoDB connections ourselves - the underlying libraries are taking care of that for us.</p>
<p>The <code class="prettyprint lang-clj">initialize</code> function is referenced in lein-ring plugin configuration and will be invoked upon ring application initialization.</p>
<h2>Accessing the data with MongoDB</h2>
<p>To separate our application from underlying persistence layer, we will create seperate service namespaces for basic data integration. Fortunately, most of the work is done by congomongo library and we can use native Clojure data structures in all layers.</p>
<p>Data operations are defined in three namespaces:</p>
<ul>
<li><a href="https://github.com/tlipski/ganelon-tutorial/blob/PART2_WIDGETS/src/ganelon/tutorial/services/meetup.clj">src/ganelon/tutorial/services/meetup.clj</a> - meetup entity management.</li>
<li><a href="https://github.com/tlipski/ganelon-tutorial/blob/PART2_WIDGETS/src/ganelon/tutorial/services/meetup_time.clj">src/ganelon/tutorial/services/meetup_time.clj</a> - possible times for each meetup.</li>
<li><a href="https://github.com/tlipski/ganelon-tutorial/blob/PART2_WIDGETS/src/ganelon/tutorial/services/meetup_time.clj">src/ganelon/tutorial/services/invitation.clj</a> - meetup invitations.</li>
</ul>
<p>All of functions in these namespace are quite simple and mostly cover congomongo (<code class="prettyprint lang-clj">somnium.congomongo</code> is imported as <code class="prettyprint lang-clj">db</code>) references, for example:</p>
<pre class="prettyprint lang-clj">
(defn retrieve-list [meetup-id]
(db/fetch :meetup-times :where {:meetup-id meetup-id}
:sort {:date 1 :time 1}))
(defn add-time! [meetup-id date time]
(when (empty? (db/fetch :meetup-times :where
{:meetup-id meetup-id :date date :time time}))
(db/insert! :meetup-times {:meetup-id meetup-id :date date
:time time :create-time (java.util.Date.)
:accepted []})))
</pre>
<h2>Defining widgets</h2>
<p>Finally, having all the other components in place, we are able to create some Ganelon-specific widgets and actions!</p>
<h3>New meetup widget</h3>
<p>The new meetup widget consists of two parts, listed below. Entire definition is contained in <a href="https://github.com/tlipski/ganelon-tutorial/blob/PART2_WIDGETS/src/ganelon/tutorial/widgets/meetup_add.clj">ganelon.tutorial.widgets.meetup-add</a> namespace.</p>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1IVO1-uE_TTExuctK0PiuZ9jcGi79xZbK89ihGmBl15lHtZeZxzVTq6y4V8Iy24sfLrK_8BO8cOwq-glMVm9fL6ZdBy4dDNOeaOl8UMqBXktQrfz4jhFU5a8vuLrwyDvkGCUaGZWHPU0/s1600/add-meetup-widget.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1IVO1-uE_TTExuctK0PiuZ9jcGi79xZbK89ihGmBl15lHtZeZxzVTq6y4V8Iy24sfLrK_8BO8cOwq-glMVm9fL6ZdBy4dDNOeaOl8UMqBXktQrfz4jhFU5a8vuLrwyDvkGCUaGZWHPU0/s1600/add-meetup-widget.png" /></a></div>
<p>First of all, we have widget definition. As you can see below, it is a standard Clojure function, and it is invoked explicitly from Clojure code - <code class="prettyprint lang-clj">ganelon.tutorial.pages.routes/meetup-layout</code> to be exact.</p>
<pre class="prettyprint lang-clj">
(defn new-meetup-widget []
(widgets/with-div
[:h1 "New meetup"]
[:p "Please provide meetup details:"]
(widgets/action-form "meetup-create" {} {:class "form well"}
[:div.control-group [:label.control-label {:for "inputTitle"} "Title"]
[:div.controls
[:input#inputTitle {:placeholder "Title for a meetup" :type "text"
:name "title"
:required "1"}]]]
[:div.control-group [:label.control-label {:for "inputPlace"} "Place"]
[:div.controls
[:input#inputPlace {:placeholder "Place for a meetup" :type "text"
:name "place"
:required "1"}]]]
[:div.control-group
[:div.controls
[:button.btn.btn-primary.btn-large {:type "submit"} "Create"]]])))
</pre>
<p>We use Hiccup to generate HTML form, styled by Bootstrap. One significant difference is that we don't use <code class="prettyprint lang-clj">[:form]</code> HTML tag, but rather <a href="http://ganelon.tomeklipski.com/doc/ganelon.web.widgets.html#var-action-form"><code class="prettyprint lang-clj">ganelon.web.widgets/action-form</code></a> function. This function allows us to reference a Ganelon action to be invoked upon form submission - client side.</p>
<p>Action <code class="prettyprint lang-clj">meetup-create</code>, creating new meetup is defined with a help of a <a href="http://ganelon.tomeklipski.com/doc/ganelon.web.actions.html#var-defjsonaction"><code class="prettyprint lang-clj">ganelon.web.actions/defjsonaction</code></a> macro.</p>
<pre class="prettyprint lang-clj">
(actions/defjsonaction "meetup-create" [title place]
(let [id (meetup/create! title place)]
[(ui-operations/open-page (str "/meetup/edit/" id))]))
</pre>
<p>The action itself is pretty straightforward:</p>
<ul>
<li>First we create new meetup in MongoDB using form params - the destructuring syntax underneath is Compojure's, since we are using its handler infrastructure.</li>
<li>Then, we return one operation to be performed - to redirect the browser to meetup's edit page, containing its random and unique id.</li>
</ul>
<p>In the next part of the tutorial, this action will change and will not reload entire page - and we will use custom JavaScript actions to achieve that.</p>
<h3>Meetup details widget (and sub-widgets)</h3>
<p>Meetup details widget is more complicated than the previous one, since it has to support not only edition of meetup place and title, but also:</p>
<ul>
<li>Allow the user to manage possible times for a meetup with response statuses, which is implemented in <a href="https://github.com/tlipski/ganelon-tutorial/blob/PART2_WIDGETS/src/ganelon/tutorial/widgets/meetup_times.clj"><code class="prettyprint lang-clj">ganelon.tutorial.widgets.meetup-times</code></a> namespace.</li>
<li>Manage meetup invitations - in <a href="https://github.com/tlipski/ganelon-tutorial/blob/PART2_WIDGETS/src/ganelon/tutorial/widgets/meetup_invitations.clj"><code class="prettyprint lang-clj">ganelon.tutorial.widgets.meetup-invitations</code></a> namespace.</li>
</ul>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMeXwbuvjhU6upzgtEb1_DqYfGgfIqzjwjsyn6ADlhh9J27I3Fvtq3wqtsLWQPtvHjOI-g2BfWdCgTzB6nGhlRVeb3860UNvQVeDqX34O8SJzjhqF3Y6NA0dBXpZiA3kiZ6qVwHipLrdg/s1600/meetup-details-widget.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMeXwbuvjhU6upzgtEb1_DqYfGgfIqzjwjsyn6ADlhh9J27I3Fvtq3wqtsLWQPtvHjOI-g2BfWdCgTzB6nGhlRVeb3860UNvQVeDqX34O8SJzjhqF3Y6NA0dBXpZiA3kiZ6qVwHipLrdg/s1600/meetup-details-widget.png" /></a></div>
<p>Please observe, that the main meetup details widget is referencing sub-widgets as functions: <code class="prettyprint lang-clj">meetup-edit-form-widget</code>, <code class="prettyprint lang-clj">meetup-times/meetup-times-widget</code> and <code class="prettyprint lang-clj">meetup-invitations/meetup-invitations-widget</code>. This way, we can decompose our web application into a set of widgets with clearly defined dependencies.</p>
<pre class="prettyprint lang-clj">
(defn meetup-details-widget [meetup-id]
(widgets/with-div
(let [meetup (meetup/retrieve meetup-id)]
[:div
[:div {:style "padding-top: 20px;"}
(meetup-edit-form-widget meetup)]
(meetup-times/meetup-times-widget meetup-id)
(meetup-invitations/meetup-invitations-widget meetup-id)])))
</pre>
<p>As we have more possibilities here than just meetup creation, we can make the form more dynamic. For example, we will capture <code class="prettyprint lang-clj">:onchange</code> JavaScript events for meetup title and place to update the fields in MongoDB without save button. Even more, we will display a confirmation label next to a saved field.</p>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrkND2ZZ8sKz6X0hwNCzdm5Mi5pitDy5zNLxTqqSF78mMLdCxXTaIcRAfEytHCWTk3BgdvZ3eFr6gm3X7FEzzQYjCJHIx8sdXo1HEXMaJOmhcYuzhCj0dFiearDEixmf5-9qS-i0pIKl4/s1600/meetup-edit-widget.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrkND2ZZ8sKz6X0hwNCzdm5Mi5pitDy5zNLxTqqSF78mMLdCxXTaIcRAfEytHCWTk3BgdvZ3eFr6gm3X7FEzzQYjCJHIx8sdXo1HEXMaJOmhcYuzhCj0dFiearDEixmf5-9qS-i0pIKl4/s1600/meetup-edit-widget.png" /></a></div>
<p>In meetup edition widget definition, we will add standard HTML attributes, referencing our Ganelon action (meetup-title-update) - but client-side! As Ganelon has information about defined actions, there is an interface available as a standard Compojure route (by default under /ganelon/actions.js), publishing all these functions to be referenced in JavaScript client-side:</p>
<pre class="prettyprint lang-clj">
(defn meetup-edit-form-widget [meetup]
(widgets/with-div
[:h1 "Meetup details"]
(let [url (str (web-helpers/current-request-host-part) "/meetup/edit/"
(:_id meetup))]
[:p "Meetup admin url: " [:a {:href url } url]])
[:form.form-horizontal.well
[:div.control-group [:label.control-label {:for "inputTitle"} "Title"]
[:div.controls [:input#inputTitle.input-xlarge
{:placeholder "Title for a meetup" :type "text"
:value (:title meetup)
:onkeypress "$('#update-title-loader > *').fadeOut();"
<b>:onchange (str "GanelonAction.meetup_title_update('"
(:_id meetup)
"', this.value);")</b>
:name "title"
:required "1"}]
[:span#update-title-loader]]]
[:div.control-group [:label.control-label {:for "inputPlace"} "Place"]
[:div.controls [:input#inputPlace.input-xlarge
{:placeholder "Place for a meetup" :type "text"
:value (:place meetup)
:onkeypress "$('#update-place-loader > *').fadeOut();"
<b>:onchange (str "GanelonAction.meetup_place_update('"
(:_id meetup)
"', this.value);")</b>
:name "place"
:required "1"}]
[:span#update-place-loader]]]]))
</pre>
<p>Once more, we are using standard Bootstrap 2 classes here to render a form.</p>
<p>Actions update meetup details in MongoDB, but also update form elements with jQuery's fade effect, provided by <a href="http://ganelon.tomeklipski.com/doc/ganelon.web.ui-operations.html#var-fade"><code class="prettyprint lang-clj">ganelon.web.ui-operations/fade</code></a> function - for example:</p>
<pre class="prettyprint lang-clj">
(actions/defjsonaction "meetup-title-update" [id title]
(meetup/update! id :title title)
[(<b>ui-operations/fade</b> (str ".meetup-title-" id)
(hiccup.util/escape-html title))
(<b>ui-operations/fade</b> "#update-title-loader"
(hiccup.core/html [:span {:onmouseover "$(this).fadeOut();"}
" " [:i.icon-check] " Saved"]))])
</pre>
<p>You are welcome to browse the operations available in <a href="http://ganelon.tomeklipski.com/doc/ganelon.web.ui-operations.html"><code class="prettyprint lang-clj">ganelon.web.ui-operations</code></a> namespace - but please note, that it is also very easy to define your own, more fitting for you apps specific needs!</p>
<p>Meetup times widget allows us to add new invitations and to list and manage existing ones - including invitation confirmation status for each time and invitation. The whole implementation is available in <a href="https://github.com/tlipski/ganelon-tutorial/blob/PART2_WIDGETS/src/ganelon/tutorial/widgets/meetup_times.clj"><code class="prettyprint lang-clj">ganelon.tutorial.widgets.meetup-times</code></a> namespace.</p>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrS8l5ePal8MlKnMkanAcPVnF8VynO0IlRDJUbIv8WghdCumVX6MNhxy0F35M9E9k-nlVfQ0BqdOVJyhbLJ5uveys8GHvuWdT3XEm1J9O0It_SMV05us5qmJYioArIob7ihkskES4NCYg/s1600/meetup-times.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrS8l5ePal8MlKnMkanAcPVnF8VynO0IlRDJUbIv8WghdCumVX6MNhxy0F35M9E9k-nlVfQ0BqdOVJyhbLJ5uveys8GHvuWdT3XEm1J9O0It_SMV05us5qmJYioArIob7ihkskES4NCYg/s1600/meetup-times.png" /></a></div>
<pre class="prettyprint lang-clj">
(defn toggle-meetup-times-button [t inv editable-invitation-ids]
(let [accepted? (some #{(:_id inv)} (:accepted t))]
(widgets/with-div
[:div {:style (str "padding: 8px; " (if accepted?
"background-color: #5bb75b"
"background-color: #9da0a4"))}
(if (or (not editable-invitation-ids) (some #{(:_id inv)}
editable-invitation-ids))
(widgets/action-loader-link "meetup-times-toggle-invitation"
{:invitation-id (:_id inv)
:id (:_id t)
:value (not accepted?)} {}
(if accepted? [:i.icon-thumbs-up ] [:i.icon-thumbs-down ]))
" ")])))
(defn meetup-times-list-widget [meetup-id editable-invitation-ids]
(widgets/with-widget "meetup-times-list-widget"
(let [times (meetup-time/retrieve-list meetup-id)]
(if (not-empty times)
(let [invitations (invitation/retrieve-list meetup-id)]
[:table.table.table-striped.table-hover {:style "width: initial"}
[:thead [:tr
(when-not editable-invitation-ids [:th ])
[:th "Date"] [:th "Time"]
(for [inv invitations]
[:th [:small
(hiccup.util/escape-html (:name inv))]])]]
(for [t times]
[:tr (when-not editable-invitation-ids
[:td (widgets/action-link "meetup-remove-time"
{:id (:_id t)} {} [:i.icon-remove ])])
[:td (:date t)] [:td (:time t)]
(for [inv invitations]
[:td {:style "text-align: center; padding:0px;
border-left: 1px solid #ccc"}
(toggle-meetup-times-button t
inv editable-invitation-ids)])])])
[:div.alert [:i "No meetup times defined yet!"]]))))
</pre>
<p>Please observe, that we are using widgets in loop here - as <code class="prettyprint lang-clj">ganelon.web.widgets/with-div</code> macro binds widget each time to a unique and random value, we can tons of instances of the same widget side by side.</p>
<p>We can also define a function which will return Ganelon UI operation refreshing times list - to be invoked externally (e.g. from invitations list widget):</p>
<pre class="prettyprint lang-clj">
(defn refresh-meetup-times-list-widget-operations [meetup-id]
(ui-operations/fade "#meetup-times-list-widget"
(meetup-times-list-widget meetup-id nil)))
</pre>
<p>This operation references widget by id attribute, allowing us to invoke it without widget-id context set.</p>
<p>The <code class="prettyprint lang-clj">meetup-times-toggle-invitation</code> action takes advantage of Compojure's route params support, allowing us to access them by name directly:</p>
<pre class="prettyprint lang-clj">
(actions/defwidgetaction "meetup-times-toggle-invitation"
[<b>id invitation-id value</b>]
(if (boolean (Boolean. value))
(meetup-time/accept-time! id invitation-id)
(meetup-time/reject-time! id invitation-id))
(toggle-meetup-times-button (meetup-time/retrieve id)
(invitation/retrieve invitation-id) [invitation-id]))
</pre>
<p><code class="prettyprint lang-clj">meetup-add-time</code> action itself provides simple validation mechanism:</p>
<pre class="prettyprint lang-clj">
(actions/defjsonaction "meetup-add-time" [id date time]
(if-let [new-mu (meetup-time/add-time! id date time)]
;success
[(ui-operations/make-empty "#meetup-add-time-message")
(ui-operations/fade "#meetup-times-list-widget"
(meetup-times-list-widget id nil))]
;error - such time already exists
(ui-operations/fade "#meetup-add-time-message"
(hiccup.core/html
[:div.alert
[:button.close {:type "button" :data-dismiss "alert"} "×"]
[:p "Such date & time combination already exists!" ]]))))
</pre>
<p><code class="lang-clj prettyprint">ganelon.web.ui-operations/make-empty</code> operation simply removes all contents from designated dom element.</p>
<p>Meetup invitations list widget works on similar principle as meetup times widget listed above:</p>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKr9EKWBC3KAYcQ_7mK-Epy3a9BsXu0GgWe_nfeS12y9n9ypcsSfP1-6_IR_W61iWsXBSIwT_d2eknjPjAypVsjiynIpaT0euDx8kXRWt6YJ3QjEGtV5_aJZMna5l9scLEWm6dabBIO0A/s1600/meetup-invitations.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKr9EKWBC3KAYcQ_7mK-Epy3a9BsXu0GgWe_nfeS12y9n9ypcsSfP1-6_IR_W61iWsXBSIwT_d2eknjPjAypVsjiynIpaT0euDx8kXRWt6YJ3QjEGtV5_aJZMna5l9scLEWm6dabBIO0A/s1600/meetup-invitations.png" /></a></div>
<pre class="prettyprint lang-clj">
(defn meetup-invitations-widget [meetup-id]
(widgets/with-widget "meetup-invitations-widget"
[:h2 "Meeting invitations"]
(widgets/action-form "invitation-create"
{:meetup-id meetup-id}
{:class "form-horizontal well"}
[:span "Recipient's name:"] " "
[:input {:type "text" :name "name" :required "1"}] " "
[:button.btn.btn-primary "Create new invitation"]
)
(let [invitations (invitation/retrieve-list meetup-id)]
(if (empty? invitations)
[:div.alert
"No invitations created yet. Please use the form above to add some!"]
(for [inv invitations]
[:p
[:b (hiccup.util/escape-html (:name inv))] [:br]
"Link: " [:a {:href
(str (web-helpers/current-request-host-part)
"/i/" (:_id inv))}
(str (web-helpers/current-request-host-part)
"/i/" (:_id inv))]
(widgets/action-link "invitation-cancel"
{:meetup-id meetup-id :id (:_id inv)}
{:class "pull-right"}
[:i.icon-remove] " Cancel")])))))
(actions/defwidgetaction "invitation-create" [name meetup-id]
(invitation/create! meetup-id name)
(actions/put-operation!
(meetup-times/refresh-meetup-times-list-widget-operations
meetup-id))
(meetup-invitations-widget meetup-id))
(actions/defwidgetaction "invitation-cancel" [meetup-id id]
(invitation/delete! id)
(actions/put-operation!
(meetup-times/refresh-meetup-times-list-widget-operations
meetup-id))
(meetup-invitations-widget meetup-id))
</pre>
<p>One thing worth noticing is that we put operation refreshing meetup times widget upon invitation creation or deletion. Instead of accessing widget functions for meetup times directly, we call a wrapper function which encapsulates all the necessary logic - e.g. DIV identification and returns ready to use Ganelon UI operation.</p>
<h3>Invitation widget</h3>
<p>Invitation widget is used by meetup attendees and allows them only to confirm or reject their presence at the meetup at given time. Whole implementation is provided withing <a href="https://github.com/tlipski/ganelon-tutorial/blob/PART2_WIDGETS/src/ganelon/tutorial/widgets/invitation_details.clj"><code class="prettyprint lang-clj">ganelon.tutorial.widgets.invitation-details</code></a> namespace.</p>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxMiK3Cnd5Pt8UZcbxdaZo6U1ca2FlTge_Wh0XC6QfSZGQDSMU80PlHkz5yWsqRE45eD6SoiEvSd4X0w8vtjIxAObrnuuxQKPT4y0FL0VCsBIbVxsuBIdDV7jWPHK_kNnptT7mKk7l6Jw/s1600/invitiations-widget.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxMiK3Cnd5Pt8UZcbxdaZo6U1ca2FlTge_Wh0XC6QfSZGQDSMU80PlHkz5yWsqRE45eD6SoiEvSd4X0w8vtjIxAObrnuuxQKPT4y0FL0VCsBIbVxsuBIdDV7jWPHK_kNnptT7mKk7l6Jw/s1600/invitiations-widget.png" /></a></div>
<pre class="prettyprint lang-clj">
(defn invitation-details-widget [id]
(widgets/with-div
(if-let [inv (invitation/retrieve id)]
(let [meetup (meetup/retrieve (:meetup-id inv))]
[:div
[:div [:h2 (hiccup.util/escape-html (:title meetup))]
[:p "Located at: " [:b (hiccup.util/escape-html (:place meetup))]]
[:p "Sent to: " [:b (hiccup.util/escape-html (:name inv))]]]
[:h2 "Confirm your presence"]
[:p "Please mark times & dates that are best for you:"]
(meetup-times/meetup-times-list-widget (:meetup-id inv) [id])])
[:div.alert.alert-error [:h2 "Invitation not found"]
[:p "Invitation with a supplied id has not been found.
Please make sure that the link is correct."]])))
</pre>
<p>Please note, that we are re-using <code class="prettyprint lang-clj">meetup-times/meetup-times-list-widget</code> to provide meetup times and to allow the attendee to confirm or negate his/her presence at the meetup. We do so by providing additional parameter to function invocation, limiting displayed invitations only to the currently used.</p>
<h2>Summary</h2>
<p>In this blog post we have learned, that it is very easy to build dynamic, AJAX-oriented web application entirely in Clojure, server side. Even better, such approach allows us to re-use existing components in different scenarios. We don't have to stick with our application being single-page and we are totally 100% SEO friendly in such approach, as widgets are used both in standard HTML page rendering as in updates to the page itself.</p>
<p>In the next post I will show how to add middleware for security handling and how to define custom Ganelon UI operations in JavaScript.</p>
TLhttp://www.blogger.com/profile/02471354092762652778noreply@blogger.com0tag:blogger.com,1999:blog-9074762293446716009.post-31283452628371748322013-08-20T15:00:00.000+01:002013-08-20T15:00:00.945+01:00Ganelon tutorial, part 1: basic setup, routes & templates<b>This part of Ganelon tutorial shows how to start <a href="http://ganelon.tomeklipski.com/">Ganelon</a>-enabled project with leiningen, configure basic pages with Hiccup templates and run the jetty-ring adapter. You can see the complete application for this step in a GitHub repository: <a href="https://github.com/tlipski/ganelon-tutorial/tree/PART1_ROUTES">https://github.com/tlipski/ganelon-tutorial/tree/PART1_ROUTES</a>.</b><br />
<br />
<i>If you want to see the final result of an entire tutorial, you can view the tutorial app running live at <a href="http://ganelon-tutorial.tomeklipski.com/">http://ganelon-tutorial.tomeklipski.com</a> or you can check out/star/fork the source code at <a href="http://github.com/tlipski/ganelon-tutorial">http://github.com/tlipski/ganelon-tutorial</a>.</i><br />
<i><br /></i>
For more information regarding Ganelon, you can visit the project website: <a href="http://ganelon.tomeklipski.com/">http://ganelon.tomeklipski.com</a>.<br />
<br />
<h2>
Creating Ganelon project</h2>
<div>
Ganelon is just a standard Leiningen/Maven/Gradle/etc. dependency, so you can just use <code class="prettyprint">lein new</code> to start a new project or set up the <code class="prettyprint">project.clj</code> file and <code class="prettyprint">src/</code>, <code class="prettyprint">script/</code> and <code class="prettyprint">resources/</code> directories manually.</div>
<br />
<p>In either way, it is important that the project.clj file contains a dependency on ganelon 0.9.0 library. What is worth mentioning, is that ganelon comes with dependencies on lib-noir, Compojure & hiccup, so in simple cases, it is enough to just add this one dependency when it comes to Clojure web apps.</p>
<p>Ganelon 0.9.0 is deployed to clojars, so it will get fetched alongside other Clojure libraries.</p>
<p>Our <code class="prettyprint">project.clj</code> at this moment looks like this:</p>
<pre class="prettyprint lang-clj">
(defproject ganelon-tutorial "0.9-SNAPSHOT"
:description "Ganelon tutorial"
:url "http://ganelon.tomeklipski.com"
:dependencies [[ganelon "0.9.0"]
[org.clojure/clojure "1.5.1"]]
:license {:name "Eclipse Public License - v 1.0"
:url "http://www.eclipse.org/legal/epl-v10.html"
:distribution :repo
:comments "same as Clojure"}
:plugins [[lein-ring "0.8.6"]]
:ring {:handler ganelon.tutorial/handler :init ganelon.tutorial/initialize})
</pre>
<p>As we can see, there is only one dependency other than Clojure 1.5.1 - it is for ganelon 0.9.0. There is also a lein-ring configuration with a pretty standard settings - an initialize function, which at this moment does nothing but will establish a connection pool for MongoDB and a handler reference.</p>
<h2>Handler definition</h2>
<p>Our handler will be using Ganelon's simple mechanism for establishing routes - and we will mix <a href="http://ganelon.tomeklipski.com/doc/ganelon.web.app.html#var-app-handler"><code class="prettyprint">app-handler</code></a> from Ganelon with standard Ring middleware.</p>
<p>You can add your own handlers and middleware here and it will just work. If you don't want to or cannot use
Compojure routes, you can just skip this step altogether and use raw Ring handlers from <a href="http://ganelon.tomeklipski.com/doc/ganelon.web.actions.html">ganelon.web.actions</a> package.</p>
<pre class="prettyprint lang-clj">
(def handler
(->
(ganelon.web.app/app-handler
(ganelon.web.app/javascript-actions-route))
middleware/wrap-x-forwarded-for
(ring.middleware.stacktrace/wrap-stacktrace)
(ring.middleware.reload/wrap-reload {:dirs ["src/ganelon/tutorial/pages"]})))
</pre>
<p class="small"><a href="https://github.com/tlipski/ganelon-tutorial/blob/PART1_ROUTES/src/ganelon/tutorial.clj">View the entire ganelon/tutorial.clj file.</a></p>
<p>Please also note, that we are using a convienent <code class="prettyprint">ring.middleware.reload/wrap-reload</code> middleware, which will reload all the changes to Clojure files in specified directories - <code class="prettyprint">src/ganelon/tutorial/pages</code> in this case.</p>
<h2>Defining the Hiccup templates</h2>
<p>The simplest way (but sometimes not the most effective) to achieve common layout and HTML code for pages with Hiccup is to define them as functions, accepting page content as parameters:</p>
<pre class="prettyprint lang-clj">
(defn layout [& content]
(hiccup/html5
[:head [:meta {:name "viewport" :content "width=device-width, initial-scale=1.0"}]
[:title "Ganelon tutorial - Meetups"]
;real life site should use CDN/minify to serve static resources
(hiccup/include-css "/ganelon/css/bootstrap.css")
(hiccup/include-css "/ganelon/css/bootstrap-responsive.css")
]
[:body.default-body [:div#navbar (navbar)]
[:div.container {:style "padding-top: 70px"}
content]
[:footer {:style "opacity:0.9; text-align: center; padding: 30px 0; margin-top: 70px; border-top: 1px solid #E5E5E5; color: #f6f6f6; background-color: #161616;"}
[:div.container [:p "The Ganelon framework has been designed, created and is maintained by " [:a {:href "http://twitter.com/tomeklipski"} "@tomeklipski"] "."]
[:p "The code is available under " [:a {:href "http://opensource.org/licenses/eclipse-1.0.php"} "Eclipse Public License 1.0"] "."]
[:p [:a {:href "http://github.com/tlipski/ganelon-tutorial"} "View the sources on GitHub."]]
[:p "This interactive tutorial runs on " [:a {:href "http://cloudbees.com"} "CloudBees"]
" and " [:a {:href "http://mongohq.com"} "MongoHQ"] "."]
]]]))
</pre>
<p class="small"><a href="https://github.com/tlipski/ganelon-tutorial/blob/PART1_ROUTES/src/ganelon/tutorial/pages/common.clj">View the entire ganelon/tutorial/pages/common.clj file.</a></p>
<p>As you can see, we are using standard Bootstrap2 CSS/classes - nothing fancy.</p>
<p>We have also defined a helper layout with common UI components and put it <a href="https://github.com/tlipski/ganelon-tutorial/blob/PART1_ROUTES/src/ganelon/tutorial/pages/routes.clj">routes.clj</a> file:
<pre class="prettyprint lang-clj">
(defn meetup-layout [& contents]
(common/layout
[:div.row-fluid [:div.span3 [:div {:style "border: 1px dashed #363636"} "TODO - new meetup widget here"]
[:div {:style "border: 1px dashed #363636"} "TODO - meetup list widget here"]]
[:div.span1 ]
[:div.span8 [:div#contents contents]]]))
</pre>
<h2>Defining routes</h2>
<p>With templates already defined, we can set up our routes. They will contain placeholders for <a href="http://ganelon.tomeklipski.com/ajax#widgets">widgets</a>, as these will be added in the next steps of the tutorial:</p>
<pre class="prettyprint lang-clj">
(dyna-routes/defpage "/" []
(meetup-layout
[:div.hero-unit [:h1 "Welcome"]
[:p "Welcome to the interactive tutorial for " [:a {:href "http://ganelon.tomeklipski.com"} "Ganelon micro-framework."]]
[:p "This sample application used to manage meetups provides links to display source of every widget and action used.
In addition to that, each widget has a dashed border to mark its boundary."]]))
(dyna-routes/defpage "/meetup/edit/:id" [id]
(meetup-layout
[:div {:style "border: 1px dashed #363636"} "TODO - meetup details widget here"]))
(dyna-routes/defpage "/i/:id" [id]
(meetup-layout
[:div {:style "border: 1px dashed #363636"} "TODO - invitation details widget here"]))
</pre>
<p>The <code class="prettyprint">ganelon.web.dyna-routes/defpage</code> macro simply creates <code class="prettyprint">compojure.core/ANY</code> route and adds it to a list maintained in the <code class="prettyprint">ganelon.web.dyna-routes</code> namespace. The routes can be grouped for easier management and middleware can be defined in a similar way. More information is available on the <a href="http://ganelon.tomeklipski.com/routing">Routing page</a> of <a href="http://ganelon.tomeklipski.com/">Ganelon project website</a>.</p>
<h2>Running the project</h2>
<p>After that, we have two general ways to run our project and there is nothing specific to Ganelon about them:</p>
<p>The simplest is to use lein-ring plugin and run <code class="prettyprint">lein ring server</code>:</p>
<pre class="prettyprint">
$ lein ring server
2013-08-19 21:39:41.474:INFO:oejs.Server:jetty-7.6.1.v20120215
2013-08-19 21:39:41.502:INFO:oejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:3000
Started server on port 3000
</pre>
<p>We can also use <code class="prettyprint">lein ring uberjar</code> to obtain an executable jar file.</p>
<p>If we are using some IDE not compliant with Leiningen - for example IntelliJ IDEA, we can create a simple <a href="https://github.com/tlipski/ganelon-tutorial/blob/PART1_ROUTES/script/run.clj">script</a> to run our jetty-ring adapter:</p>
<pre class="prettyprint lang-clj">
(ns run
(:require [ganelon.tutorial]
[ring.adapter.jetty :as jetty]))
(defonce SERVER (atom nil))
(defn start-demo [port]
(jetty/run-jetty ganelon.tutorial/handler {:port port :join? false}))
(ganelon.tutorial/initialize)
(let [port (Integer. (get (System/getenv) "PORT" "3000"))]
(swap! SERVER (fn [s] (when s (.stop s)) (start-demo port))))
</pre>
<p>This scripts just optionally stops and then starts jetty adapter for our handler - but can be loaded for example into IntelliJ IDEA's REPL.</p>
<p>After applying either method, we can just navigate to <a href="http://localhost:3000/">http://localhost:3000/</a> to see the running application.</p>
<h2>What's next?</h2>
<p>In the next part of the tutorial, I will show how to build widgets, actions and use them to propagate data back and forth between a web app and a MongoDB instance.</p>TLhttp://www.blogger.com/profile/02471354092762652778noreply@blogger.com0tag:blogger.com,1999:blog-9074762293446716009.post-39603507413320449102013-08-19T14:30:00.000+01:002013-08-19T14:30:00.754+01:00Ganelon tutorial, intro<h2>
What is Ganelon?</h2>
<div>
<a href="http://ganelon.tomeklipski.com/">Ganelon</a> is a microframework of mine, built to ease the pain of using AJAX in Ring/Compojure based Clojure web applications. </div>
<div>
The microframework is not intended as an end-to-end framework, solving all the problems of building web applications in Clojure, but rather as a tool that solves one particular problem in an elegant and simple way. </div>
<div>
Other problems, such as routing, templating or integration with underlaying HTTP(S) server architecture have their own, well established solutions. For example there is Compojure or Mustache for routing, Hiccup or Clabango for templating, etc. </div>
<div>
For a full list, you can go to <a href="http://www.clojure-toolbox.com/">http://www.clojure-toolbox.com/</a>. Important thing to note is that by using Ganelon you are still free to apply any other Ring-compliant or client-side libraries - Ganelon simply enhances your application's capabilities, not forces you down some narrow path.</div>
<h2>
</h2>
<h2>
How does Ganelon work?</h2>
<div>
Ganelon is also different from modern, JavaScript/ClojureScript based web frameworks, as it gives a control over user interface behaviour directly to a code running server-side. JavaScript layer is very thin and provides a set of generic operations; the layer can of course be easily extended as we will see in the tutorial. More information is available in <a href="http://ganelon.tomeklipski.com/basics">the documentation</a>.</div>
<div>
As to my knowledge, there are no other Clojure frameworks built this way, but similar principle is used for example in <a href="http://vaadin.com/" target="">Vaadin</a> or <a href="http://wicket.apache.org/">Wicket</a> (Java) or in <a href="http://common-lisp.net/project/cl-weblocks/" target="">Weblocks</a> (Common LISP). The main difference is that Ganelon is not keeping the information about widget state. </div>
<h2>
</h2>
<h2>
Ganelon tutorial</h2>
<div>
In the Ganelon tutorial, I will show how to:</div>
<div>
<ul>
<li>Setup a new Ganelon project, configure basic routes and layout templates (using Hiccup) (PART 1).</li>
<li>Build reusable, dynamic elements of UI (widgets) and use MongoDB (from mongohq) as a persistence layer (PART 2).</li>
<li>Add middleware for security handling and define custom operations (PART 3).</li>
<li>Finally, I will show how can the tutorial app display its source code (PART 4).</li>
</ul>
<div>
If you want to see the final result, you can view the tutorial app running live at <a href="http://ganelon-tutorial.tomeklipski.com/">http://ganelon-tutorial.tomeklipski.com</a> or you can check out/star/fork the source codes at <a href="http://github.com/tlipski/ganelon-tutorial">http://github.com/tlipski/ganelon-tutorial</a>.</div>
<div>
<br /></div>
</div>
<div>
<br /></div>
TLhttp://www.blogger.com/profile/02471354092762652778noreply@blogger.com0tag:blogger.com,1999:blog-9074762293446716009.post-39766250061996634202013-04-18T20:17:00.000+01:002013-04-18T20:17:50.820+01:00Ganelon 0.9.0 released<b><a href="http://ganelon.tomeklipski.com/">Ganelon</a> - a micro-framework supporting server-side oriented AJAX web applications in Ring/Clojure has been released in version 0.9.0 - first publicly available non-SNAPSHOT.</b><br />
<b><br /></b>
<b>In addition to that, an interactive tutorial app (meetings management with MongoDB) has been launched at <a href="http://ganelon-tutorial.tomeklipski.com/">http://ganelon-tutorial.tomeklipski.com</a>. Source codes for it can be found in GitHub repository: <a href="https://github.com/tlipski/ganelon-tutorial">https://github.com/tlipski/ganelon-tutorial</a>. </b><br />
<b>In the following weeks, this tutorial app will be used as a base for a tutorial blog series.</b><br />
<h3>
Using Ganelon</h3>
To use Ganelon in your Compojure/Ring/Clojure web application, simply add the following dependency to your <code class="prettyprint lang-clj">project.clj</code> file:<br />
<br />
<pre class="prettyprint lang-clj"> [ganelon "0.9.0"]</pre>
For more information on using Ganelon, please visit <a href="http://ganelon.tomeklipski.com/">http://ganelon.tomeklipski.com</a>.
<div><br /></div>
<h3>
Production use</h3>
<div>
Ganelon is used to build user interface for <a href="https://mydailysocial.info/">Daily Social</a> - social news aggregator delivering top stories to Pocket or Readability. So it is used in production for almost half a year. The <a href="http://ganelon.tomeklipski.com/">demo</a> and <a href="http://ganelon-tutorial.tomeklipski.com/">interactive tutorial </a>sites are written using Ganelon as well.</div>
<div>
<br /></div>
<div>
I think that the best thing to do now - is to give a Ganelon a try and see how it works out for yourself.</div>
<div>
Any issues/fixes can be reported directly to the GitHub repo at <a href="http://github.com/tlipski/ganelon">http://github.com/tlipski/ganelon</a>.</div>
<div>
<br /></div>
<h3>
Most important features</h3>
As this is a first major release of Ganelon, instead of changes introduced, I will simply highlight most important features:<br />
<ul>
<li>AJAX support for <a href="http://ganelon.herokuapp.com/doc/ganelon.web.ui-operations.html">client-side operations</a>, including basic functions and almost whole <a href="http://api.jquery.com/category/manipulation/">jQuery | Manipulation</a> library.</li>
<ul>
<li>Ability to add custom client-side operations.</li>
<li>Additional libraries for Bootstrap Modal JavaScript and jquery gritter plugin (Growl-style notifications).</li>
</ul>
<li>Support for definition of <a href="http://ganelon.herokuapp.com/doc/ganelon.web.actions.html">AJAX actions</a> as Compojure routes.</li>
<li>Basic <a href="http://ganelon.herokuapp.com/doc/ganelon.web.widgets.html">functions</a> rendering client-side code for HTML/JavaScript controls invoking AJAX actions.</li>
<li>Support for <a href="http://ganelon.herokuapp.com/doc/ganelon.web.dyna-routes.html">distributed configuration</a> of Compojure routes - somewhat akin to Noir's <code class="prettyprint lang-clj">defpage</code> macro.</li>
</ul>
<div>
Helper library for Ganelon - <a href="https://github.com/tlipski/ganelon-util">ganelon-util </a>has been released as well - the current version is 0.8.0. Ganelon adds subdependency on ganelon-util, so you don't have to worry about adding it manually.</div>
TLhttp://www.blogger.com/profile/02471354092762652778noreply@blogger.com0tag:blogger.com,1999:blog-9074762293446716009.post-47265729583605755612013-04-11T22:57:00.000+01:002013-04-11T22:57:07.171+01:00Running and debugging Clojure code with Intellij IDEA<b>In the following blog post, I will show how to install La Clojure plugin, import project from leiningen to IntelliJ IDEA, how to interact with REPL and finally how to use IDEA's debugger to debug Clojure code run from REPL. </b><br />
<b>These features make Clojure development with IDEA a real pleasure.</b><br />
<br />
<h2>
Installation of La Clojure plugin</h2>
<div>
To use Clojure with IntelliJ IDEA, we have to add La Clojure plugin. To do that, we open <b>File / Settings / Plugins</b> from a menu, than click on <b>Browse repositories...</b> button at the bottom of the Settings window (highlighted in red).</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTQANKXOPgmWtBVaAcC3mkl1K3nIJs4ibAUlml5CaExcHZnMsfw-5nrwxfprq4-KWZS_snU6Ucxw0P2JCifyGD7vYbSNxRiEsOOqBrIZDc5oAbLbd8NpfoyO90WuAObigfvLWrGPMFGQo/s1600/la_clojure_step1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTQANKXOPgmWtBVaAcC3mkl1K3nIJs4ibAUlml5CaExcHZnMsfw-5nrwxfprq4-KWZS_snU6Ucxw0P2JCifyGD7vYbSNxRiEsOOqBrIZDc5oAbLbd8NpfoyO90WuAObigfvLWrGPMFGQo/s1600/la_clojure_step1.png" /></a></div>
<div>
<br /></div>
<div>
Then, we select <b>La Clojure</b> from plugins list (we can use filter in the right top corner of the window), right click on the item and select <b>Download and Install</b> command:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJTE6c6VUsfY6oWengYlqVdPk0EdyW8JisG-M0z6RIq-qGorUkXdorTAO2F3stXBZqIu_jkW8mQjHhaUZz-URFVVpEw8PN6sqGoCMn2JTmphTSAegsoyzXpwaRe7OF68Zf9aWKv3_rH4U/s1600/la_clojure_step3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJTE6c6VUsfY6oWengYlqVdPk0EdyW8JisG-M0z6RIq-qGorUkXdorTAO2F3stXBZqIu_jkW8mQjHhaUZz-URFVVpEw8PN6sqGoCMn2JTmphTSAegsoyzXpwaRe7OF68Zf9aWKv3_rH4U/s1600/la_clojure_step3.png" /></a></div>
<div>
<br /></div>
<div>
Upon leaving the Settings window, IDEA will ask if we want to restart it - the plugin will not work unless we do.</div>
<div>
<br /></div>
<h2>
Importing leiningen project</h2>
With the La Clojure plugin ready and active, we can import a leiningen project into IntelliJ IDEA. All we have to do is to generate appropriate Maven pom.xml file:<br />
<br />
<code class="prettyprint">
lein pom</code><br />
<br />
Then, we can import the maven module using IntelliJ IDEA.<br />
<br />
Step 1: select Import Project from welcome screen:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrLyQASxvbF0cbLWEFb_jzCP4TZ5NIQcr8fbznA3v59HprtsEBYGeH2SZuxL6bZEVbjOrCm7FTjz3I_vdYwiZToGV-yRz-TBYlT3XDfIBfuMmgBBjMDzZZHLsG4AnqW6PJJGa4EV4o0Yc/s1600/idea_import.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrLyQASxvbF0cbLWEFb_jzCP4TZ5NIQcr8fbznA3v59HprtsEBYGeH2SZuxL6bZEVbjOrCm7FTjz3I_vdYwiZToGV-yRz-TBYlT3XDfIBfuMmgBBjMDzZZHLsG4AnqW6PJJGa4EV4o0Yc/s1600/idea_import.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Step 2: point IDEA to<span style="font-family: Courier New, Courier, monospace;"> pom.xml</span> (not <span style="font-family: Courier New, Courier, monospace;">project.clj</span>) file:</div>
<div class="separator" style="clear: both; text-align: center;">
<i style="text-align: left;"><br /></i></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjS2j37nc4p_vemyRBSHkR6FHdX8o39Tnezg26qxJ0U40zfazI9c7NThWYGKADT3pkfFDGzer1Djg1Hd_V8vZrlPv6sRtgDS-_uOOFnoHbSrJvpDELJJne7h4B7E9UGuUmB_5m3sDCzb98/s1600/idea_import_step2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjS2j37nc4p_vemyRBSHkR6FHdX8o39Tnezg26qxJ0U40zfazI9c7NThWYGKADT3pkfFDGzer1Djg1Hd_V8vZrlPv6sRtgDS-_uOOFnoHbSrJvpDELJJne7h4B7E9UGuUmB_5m3sDCzb98/s1600/idea_import_step2.png" /></a></div>
<div style="text-align: center;">
<br /></div>
<div style="text-align: left;">
After steps above, you can follow standard IntelliJ IDEA Maven project import procedure: confirm Maven project options, select IDE, set project name and IDEA files location for a project.</div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
One thing that has to be done manually is adding Clojure Facet to project modules. To do that, we simply select <b>File / Project</b> structure from a menu, then navigate to our module in <b>Modules</b> tab, click on a <b>+</b> sign and finally select <b>Clojure:</b><br />
<b><br /></b>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAawSr6g8CeWD0V9MY9jy98cLhz4D1XEiW5cgWHsuaMfZXwR351n6wBEvBmzu_zTNt9MacTm4-NhhNulsSnsqUm6ICOugSFoO699r38Tadw6SPsjJSSFXW3hRPXdi8kFvxq5jKSF8x4VE/s1600/idea_facet.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAawSr6g8CeWD0V9MY9jy98cLhz4D1XEiW5cgWHsuaMfZXwR351n6wBEvBmzu_zTNt9MacTm4-NhhNulsSnsqUm6ICOugSFoO699r38Tadw6SPsjJSSFXW3hRPXdi8kFvxq5jKSF8x4VE/s1600/idea_facet.png" /></a></div>
<b><br /></b></div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<h2>
Starting the REPL</h2>
<br />
<div class="separator" style="clear: both; text-align: left;">
With Maven project loaded successfully, IDEA will fetch dependencies which aren't already in Maven local repository. With these libraries fetched, we can start Clojure REPL for our project by selecting <b>Tools / Start Clojure Console </b>from a menu. </div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
We can also use the keyboard shortcut - <b>Ctrl-Shift-D</b> by default in the newest La Clojure version.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
The REPL will set a classpath for all of our libraries and sources referenced in a current module:</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjClvDM-4N2U7rHDe4JyOobfI85pvRVQglnk3-FrrJswxChQMA8ZFf_QZEkQF0SyrQ3mMu9EPvpz8u3l45tphtmEggUKUAcdWPY-b4HWzu0YUu6uVt2vlVOaeHx0mphqpGelnmULzt5Mnc/s1600/repl_start_step1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjClvDM-4N2U7rHDe4JyOobfI85pvRVQglnk3-FrrJswxChQMA8ZFf_QZEkQF0SyrQ3mMu9EPvpz8u3l45tphtmEggUKUAcdWPY-b4HWzu0YUu6uVt2vlVOaeHx0mphqpGelnmULzt5Mnc/s1600/repl_start_step1.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<h2 style="text-align: left;">
Interacting with REPL</h2>
<div style="text-align: left;">
To load a current Clojure file to REPL by a load-file function, all we have to do is to select <b>Tools / Clojure REPL / Load file to REPL</b> or use a keyboard shortcut - <b>Ctrl-Shift-L</b> by default.</div>
<div style="text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZN-eelQygz-tPsEaU29O6A4L27uWWjV8A8gubrgoA7eMuM63FaKPpZnCTRcB0X5eTC5wYaL8yGWx-cccOgUeO6BeGb1h66lKg8ycYshlMow7QOWjMSIsA8fxBu5kHrrjSrUqlUJ_xwSo/s1600/repl_start_step2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZN-eelQygz-tPsEaU29O6A4L27uWWjV8A8gubrgoA7eMuM63FaKPpZnCTRcB0X5eTC5wYaL8yGWx-cccOgUeO6BeGb1h66lKg8ycYshlMow7QOWjMSIsA8fxBu5kHrrjSrUqlUJ_xwSo/s1600/repl_start_step2.png" /></a></div>
<div style="text-align: left;">
<br /></div>
We can also use Tools / Clojure REPL menu to:<br />
<div class="separator" style="clear: both; text-align: left;">
</div>
<ul>
<li>Run selected text in REPL </li>
<li>Execute last S-Expression in REPL</li>
<li>Run top S-Expression in REPL</li>
</ul>
<h2>
Debugging with REPL</h2>
<div>
With IntelliJ IDEA we can connect a remote debugger to a REPL, allowing us to debug our Clojure code.<br />
<br />
Step 1: we have to create a Remote Debugger profile using <b>Run / Edit Configurations...</b> from a menu.<br />
To add a Remote Debugger, we have to click on the plus <b>'+'</b><b> </b>sign and select <b>'Remote'</b> configuration type:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh48mlSZRtHlSvqmJvZqtezXH4JfG67ZzlD2q0sItROcfyer9x8bbHJ-rCYr4pIwtAF4Uqw0x7SoggPpi3Gck3Hex1j7bMLiwr7J2BmkSgGJuf0_MNeNvcDHIJRpej_sZe1qgeV18mj5J8/s1600/repl_debug_step1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh48mlSZRtHlSvqmJvZqtezXH4JfG67ZzlD2q0sItROcfyer9x8bbHJ-rCYr4pIwtAF4Uqw0x7SoggPpi3Gck3Hex1j7bMLiwr7J2BmkSgGJuf0_MNeNvcDHIJRpej_sZe1qgeV18mj5J8/s1600/repl_debug_step1.png" /></a></div>
<b><br /></b>
We can adjust the settings or leave them as default. Most importantly, we have to copy command line arguments for running remote JVM, for example:<br />
<br />
<code class="prettyprint">-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=<b>5005</b></code></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: start;">
<i>The default value for port is <b>5005</b>, but it can be adjusted in case there is another process already listening on that port.</i></div>
<div class="separator" style="clear: both; text-align: start;">
<br /></div>
<div class="separator" style="clear: both; text-align: start;">
<i>It is also convienient to name this Remote Debugging session as 'Clojure REPL Debugger' for example.</i></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Step 2: apply command line arguments to REPL settings for Clojure facet in our module (<b>File / Project Structure / Modules / [our module] / Clojure / JVM arguments</b>):</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHuEsyRaDMQ8MRNm2hip_A16QwnGWPSuUm-Y4KnILSneGtvbOwsivi3uY6wr6iYnMX5YTx2GhpOoLBikA6VEW6Og9TU9excP302YwlelWJIARtNYisLu6LGw2vgdMMdOQZHcgyCFV-P9k/s1600/repl_debug_step2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHuEsyRaDMQ8MRNm2hip_A16QwnGWPSuUm-Y4KnILSneGtvbOwsivi3uY6wr6iYnMX5YTx2GhpOoLBikA6VEW6Og9TU9excP302YwlelWJIARtNYisLu6LGw2vgdMMdOQZHcgyCFV-P9k/s1600/repl_debug_step2.png" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Step 3: Start a REPL with <b>Tools / Start Clojure Console</b> or a keyboard shortcut - <b>Ctrl-Shift-D</b> by default:</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhD2e2cJFBOM97666xiTjwz9i4N0gNvbVyU-xAkRumi8o4Ll64biChrb1JQu7l_Q49f-9e_MvvqkrXAuGsjyebCAUEfSikj3oQbN8vOpZWRZVQ2r_gcwuEjNjWyzXIf1bqXt6NRpgHvmXc/s1600/repl_debug_step3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhD2e2cJFBOM97666xiTjwz9i4N0gNvbVyU-xAkRumi8o4Ll64biChrb1JQu7l_Q49f-9e_MvvqkrXAuGsjyebCAUEfSikj3oQbN8vOpZWRZVQ2r_gcwuEjNjWyzXIf1bqXt6NRpgHvmXc/s1600/repl_debug_step3.png" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<i>If the REPL is already running, it needs to be stopped and started again.</i></div>
<div class="separator" style="clear: both; text-align: left;">
<i><br /></i></div>
<div class="separator" style="clear: both; text-align: left;">
Step 4: Start Remote Debugger configuration created in step 1 with <b>Run / Debug 'Clojure REPL Debugger'</b> or a keyboard shortcut - <b>Shift-F9</b> by default.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIXplY7wyACKswoNHIUhddOf5M0fkAC_V1WQT4Z8XhMOOI0QZwmQEOCq3Ye8frmU1Lf_ZZxFODvHQ_I_Hh8qJJnJSYYVbwpIgYnPL4EQuDV1jPSnXjMVkOAALl5y26jt1pB-gbQbou2mU/s1600/repl_debug_step4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIXplY7wyACKswoNHIUhddOf5M0fkAC_V1WQT4Z8XhMOOI0QZwmQEOCq3Ye8frmU1Lf_ZZxFODvHQ_I_Hh8qJJnJSYYVbwpIgYnPL4EQuDV1jPSnXjMVkOAALl5y26jt1pB-gbQbou2mU/s1600/repl_debug_step4.png" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<i>The name of Remote Debugger configuration is dependent on the configuration created in step 1. For the sake of example, I am using 'Clojure REPL Debugger'.</i></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
And finally, with the Remote Debugger ready and connected to REPL, we can debug our Clojure code:</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0aEjkMZe9Im3xbFRJbtMF2WqsgN_dT7X0zZG7d7t5909u3NEDU9wXq2u_FvFCZHuR9MlMrsRhN__WkWFVKsaqcHgQHQYdLj69QEXW7o3b01mZe_ZkVFsNUCzNwms5pMY9V6Vc4AEUKq8/s1600/repl_debug_step5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0aEjkMZe9Im3xbFRJbtMF2WqsgN_dT7X0zZG7d7t5909u3NEDU9wXq2u_FvFCZHuR9MlMrsRhN__WkWFVKsaqcHgQHQYdLj69QEXW7o3b01mZe_ZkVFsNUCzNwms5pMY9V6Vc4AEUKq8/s1600/repl_debug_step5.png" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: center;">
<br /></div>
TLhttp://www.blogger.com/profile/02471354092762652778noreply@blogger.com11tag:blogger.com,1999:blog-9074762293446716009.post-75807212969708011992013-03-19T18:37:00.000+00:002013-04-10T20:52:27.367+01:00Introducing Ganelon - micro-framework supporting AJAX in Clojure/Ring web apps<b style="background-color: white;"><span style="border: 0px; color: #222222; font-family: Arial; font-size: 13px; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">I've recently published a simple Open Source micro-framework which allows server-side Clojure code to update web page content or invoke any JavaScript operations dynamically through XHR requests and thin JavaScript layer. </span></b><br />
<div style="background-color: white; border: 0px; color: #222222; font-family: Arial, Helvetica, sans-serif; font-size: 13px; margin: 0px; padding: 0px; vertical-align: baseline;">
<b style="font-weight: normal;"><span style="border: 0px; font-family: Arial; margin: 0px; padding: 0px; vertical-align: baseline;"><span style="border: 0px; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"><br /></span></span><span style="border: 0px; font-family: Arial; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"><a href="http://ganelon.tomeklipski.com/">Ganelon</a> is fully Ring compatible and based on Compojure and lib-noir. </span><span style="background-color: transparent; border: 0px; font-family: Arial; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">Being a AJAX focused,</span><span style="border: 0px; font-family: Arial; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"> it is not a direct replacement for </span><span style="background-color: transparent; border: 0px; font-family: Arial; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">Noir. It can rather ease the pain</span><span style="border: 0px; font-family: Arial; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"> of handling dynamic page requests in any Ring-based web application. </span><br /><span style="border: 0px; font-family: Arial; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"></span><br /><span style="border: 0px; font-family: Arial; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">The execution model is heavily influenced by Weblocks or Vaadin, but without session-statefulness and </span><span style="background-color: transparent; border: 0px; font-family: Arial; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">out-of-the-box rich user interface:</span></b><br />
<ul style="color: black; font-family: 'Times New Roman'; font-size: medium; margin-bottom: 0pt; margin-top: 0pt;"><b style="font-weight: normal;">
<li dir="ltr" style="color: #222222; font-family: Arial; font-size: 13px; line-height: 17px; list-style-type: disc; vertical-align: baseline;"><span style="border: 0px; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">Certain parts of the page can be scoped as </span><span style="border: 0px; font-weight: bold; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">Widgets</span><span style="border: 0px; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"> (for example using id attribute in HTML or ganelon.web.widgets.with-div utility macro) and referenced by id in </span><span style="border: 0px; font-weight: bold; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">Actions,</span><span style="border: 0px; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"> and updated by </span><span style="border: 0px; font-weight: bold; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">Operations</span><span style="border: 0px; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">.</span><span style="border: 0px; font-weight: bold; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"> Widgets</span><span style="border: 0px; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"> can reference </span><span style="border: 0px; font-weight: bold; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">Actions</span><span style="border: 0px; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"> to be invoked.</span></li>
<li dir="ltr" style="color: #222222; font-family: Arial; font-size: 13px; line-height: 17px; list-style-type: disc; vertical-align: baseline;"><span style="border: 0px; font-weight: bold; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">Actions </span><span style="border: 0px; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">are invoked as XHR requests and return a set of </span><span style="border: 0px; font-weight: bold; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">Operations</span><span style="border: 0px; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"> to be performed client-side. Actions are in fact simple Ring handlers.</span></li>
<li dir="ltr" style="color: #222222; font-family: Arial; font-size: 13px; line-height: 17px; list-style-type: disc; vertical-align: baseline;"><span style="border: 0px; font-weight: bold; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">Operations </span><span style="border: 0px; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">provide abstraction layer over client-side JavaScript execution - e.g. using Bootstrap Modal or just updating part of DOM tree (a </span><span style="border: 0px; font-weight: bold; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">Widget </span><span style="border: 0px; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">or any other) or open certain URL in a browser window - or just anything else that has a simple integration layer provided.</span></li>
</b></ul>
<b style="font-weight: normal;">
<span style="border: 0px; font-family: Arial; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"></span><br /><span style="border: 0px; font-family: Arial; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">The source codes are on GitHub: </span><a href="http://github.com/tlipski/ganelon" style="border: 0px; color: black; cursor: pointer; font-family: 'Times New Roman'; font-size: medium; margin: 0px; padding: 0px; text-decoration: initial; vertical-align: baseline;" target="_blank"><span style="border: 0px; color: #6611cc; font-family: Arial; font-size: 13px; margin: 0px; padding: 0px; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap;">http://github.com/tlipski/<wbr></wbr>ganelon</span></a><span style="border: 0px; font-family: Arial; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"> and demo site and documentation is available at </span><a href="http://ganelon.tomeklipski.com/" style="border: 0px; color: black; cursor: pointer; font-family: 'Times New Roman'; font-size: medium; margin: 0px; padding: 0px; text-decoration: initial; vertical-align: baseline;" target="_blank"><span style="border: 0px; color: #6611cc; font-family: Arial; font-size: 13px; margin: 0px; padding: 0px; text-decoration: underline; vertical-align: baseline; white-space: pre-wrap;">http://ganelon.tomeklipski.com</span></a><span style="border: 0px; font-family: Arial; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"><wbr></wbr>. </span><br /><span style="border: 0px; font-family: Arial; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"></span><br /><span style="border: 0px; font-family: Arial; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">Sample code (a shoutbox) is listed below:</span><br /><span style="border: 0px; font-family: Arial; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"></span><br /><span style="border: 0px; font-family: 'Courier New'; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">;A Widget, returning HTML code:</span><br /><span style="border: 0px; font-family: 'Courier New'; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">(defn box-widget []</span><br /><span style="border: 0px; font-family: 'Courier New'; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"> (widgets/with-div</span><br /><span style="border: 0px; font-family: 'Courier New'; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"> [:p "Call count (since restart): " [:b @COUNTER] ". Last 4 entries:"]</span><br /><span style="border: 0px; font-family: 'Courier New'; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"> (for [entry @ENTRIES]</span><br /><span style="border: 0px; font-family: 'Courier New'; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"> [:div.hibox [:b (:time entry)] ":&nbsp;"</span><br /><span style="border: 0px; font-family: 'Courier New'; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"> (hiccup.util/escape-html (:msg entry))])</span><br /><span style="border: 0px; font-family: 'Courier New'; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"> (widgets/action-form "say-hi" {} {:class "form-inline"}</span><br /><span style="border: 0px; font-family: 'Courier New'; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"> [:input {:name "msg" :placeholder "Say hi!" :type "text"</span><br /><span style="border: 0px; font-family: 'Courier New'; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"> :maxlength "20" :size "20"}] "&nbsp;"</span><br /><span style="border: 0px; font-family: 'Courier New'; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"> [:button {:class "btn btn-primary"} "Send!"])))</span><br /><span style="border: 0px; font-family: 'Courier New'; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"></span><br /><span style="border: 0px; font-family: 'Courier New'; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">;An Action, performing side effects and returning part of the page to be updated</span><br /><span style="border: 0px; font-family: 'Courier New'; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">(actions/defwidgetaction "say-hi" [msg]</span><br /><span style="border: 0px; font-family: 'Courier New'; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"> (swap! COUNTER inc)</span><br /><span style="border: 0px; font-family: 'Courier New'; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"> (swap! ENTRIES #(util/smart-subvec (flatten [(mkmsg msg) %]) 0 4))</span><br /><span style="border: 0px; font-family: 'Courier New'; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"> (box-widget))</span><br /><span style="border: 0px; font-family: 'Courier New'; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;"></span><br /><span style="border: 0px; font-family: Arial; margin: 0px; padding: 0px; vertical-align: baseline; white-space: pre-wrap;">JavaScript code uses jQuery and optionally Bootstrap, but the implementation is really trivial and therefore easy to replace using your favorite JS library.</span></b></div>
TLhttp://www.blogger.com/profile/02471354092762652778noreply@blogger.com0tag:blogger.com,1999:blog-9074762293446716009.post-38357436001750054322013-03-18T19:07:00.002+00:002013-03-18T19:08:26.691+00:00Porting Activiti Explorer to Liferay Portal<b>In the following blog post, I have highlighted the matter of embedding Activiti BPMS inside a Liferay Portal. With Activiti Explorer built in Vaadin and Spring, it is fairly easy to use entire components in a totally different environment. </b><br />
<b><br /></b>
<b>Proof of concept fork </b><b>with Tasks and Admin portlets is available in </b><br />
<a href="https://github.com/tlipski/Activiti/tree/master/modules/activiti-portlets">https://github.com/tlipski/Activiti/tree/master/modules/activiti-portlets</a>.<br />
<h2>
Porting Activiti Explorer to Liferay Portal</h2>
<div>
<a href="http://activiti.org/">Activiti</a> is a popular Open Source, BPMN2.0-compliant BPMS that provides a great overall package. Not only there is a library that can be embedded inside your application, but Activiti also comes with an Explorer web application, which provides an ergonomic user interface for basic operations:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFPtrAVfhtoO53Nuu59wSGMmdAc9jvUzrcmZOH9oZSLmbneMxdm0OJTwTUH1BttJ-2q1mMf5ITvQsUYdELjvOgUiT5crWtPfYsRiUclYa4Nb9_4UoqOgw6FB4dlhYghJEU4PZiTODCWJw/s1600/activiti-explorer-tasks.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="305" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFPtrAVfhtoO53Nuu59wSGMmdAc9jvUzrcmZOH9oZSLmbneMxdm0OJTwTUH1BttJ-2q1mMf5ITvQsUYdELjvOgUiT5crWtPfYsRiUclYa4Nb9_4UoqOgw6FB4dlhYghJEU4PZiTODCWJw/s640/activiti-explorer-tasks.png" width="640" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
But, Activiti is "just" a BPMS - you can run your processes with it, maybe even connect to Active Directory, Alfresco or ESB - but if you want to embed it in your intranet/extranet portal, you are almost on your own. </div>
<div>
<i><br /></i>
<i>Please note, that embedding Activiti in portal is not the same as using it to manage portal assets - for example, as a replacement for Liferay's Kaleo Workflow. The matter discussed here is more focused on using all of Activiti features, including user interface.</i><br />
<br /></div>
<div>
Luckily, this is when frameworks used in Activiti pay off - Activiti Explorer is written in Vaadin 6 and Spring. With component oriented approach from Spring and UI modularity encouraged by Vaadin, it is fairly easy to port this application to Liferay. We can extract entire components from Activiti Explorer (e.g. Tasks panel) and use them in a whole new environment.<br />
Extracting and re-using entire components requires much more work in a classical MVC approach and even may not always be possible. Also, portlet support in Vaadin makes a whole task much more pleasant.</div>
<div>
<br /></div>
<div>
<b>For the impatient: </b>The code is available in my fork of Activiti 5.13-SNAPSHOT: <a href="http://github.com/tlipski/Activiti">http://github.com/tlipski/Activiti</a>, especially in activiti-portlets module <a href="https://github.com/tlipski/Activiti/tree/master/modules/activiti-portlets">https://github.com/tlipski/Activiti/tree/master/modules/activiti-portlets</a>.</div>
<h2>
Necessary steps</h2>
<div>
"Fairly easy" does not mean without any effort. There are differences between a standard Vaadin application and a portletized one:</div>
<div>
<ul>
<li>Portlet descriptors are necessary</li>
<li>GUI should be divided into separate portlets</li>
<li>Spring-Vaadin integration is different due to<i> </i>a portal-specific request life-cycle</li>
<li>Navigation can be provided by portal - including friendly URLs, which makes for a more standard user experience.</li>
</ul>
</div>
<div>
In addition to that, some mechanisms used by Activiti are already provided by Liferay Portal and we need to bridge them:</div>
<div>
<ul>
<li>Users and Groups should be managed by Liferay Portal</li>
<li>Authentication data should be taken from a portlet container</li>
<li>Mail notifications could possibly use Liferay Mail API</li>
</ul>
<div>
Some other things have to be taken into account as well:</div>
</div>
<div>
<ul>
<li>Distribution and management of Vaadin widgetset and version - we should use Vaadin Control Panel portlet and Liferay to manage Vaadin dependencies. This requires special handling, since Activiti Explorer 5.13 utilizes <a href="http://code.dussan.org/projects/dcharts">dCharts</a> Vaadin Add-on.</li>
<li>Vaadin theme used in portlets - which is also customized by Activiti, but should be coherent with portal's look & feel.</li>
</ul>
<h2>
Current state of work</h2>
</div>
<div>
It is nice to theorize, but certain things need to be verified in practice. At this moment, my fork at <a href="http://github.com/tlipski/Activiti">http://github.com/tlipski/Activiti</a> provides:</div>
<div>
<ul>
<li><b>Activiti Tasks portlet</b>, which works in a user's context as if the user would log in to Activiti Explorer app. The portlet provides full functionality, including operations as starting a new task, fetching it from a task list, displaying task's events, assignment/ownership transfers, file attachments and so on.</li>
<li><b>Activiti Admin portlet</b> in Liferay Control Panel - most tabs seem to work fine - except Users and Groups, which should be disabled as we are using Liferay Portal to do that.</li>
</ul>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhF6mobiN1UNbidXEvNdnECtMkBHATd0GYShZtocj8JhhQJVK6mISKXC-pqok6Fy-2F6y25FqJOtdvRr7kFdZoaf_P0qLulsWl8JIDX-twiX2zJs82YRjK2BZ_gZHYKgNLH6IB51j4MeDc/s1600/activiti-liferay-tasks.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="446" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhF6mobiN1UNbidXEvNdnECtMkBHATd0GYShZtocj8JhhQJVK6mISKXC-pqok6Fy-2F6y25FqJOtdvRr7kFdZoaf_P0qLulsWl8JIDX-twiX2zJs82YRjK2BZ_gZHYKgNLH6IB51j4MeDc/s640/activiti-liferay-tasks.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQD8q9DaO0NcV_aT7F-Nf6pvV7ttosLyLb52bDHD2I1K0kjVukZvlPrATV17m7Jl6Gt5exRRicPMi-nnrxF2jv2cR-QHeZSFTzG3sXxSgk6D6roe5DxDVdc2VpAiZWwe_rc9hqI-notFA/s1600/activiti-liferay-admin.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="394" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQD8q9DaO0NcV_aT7F-Nf6pvV7ttosLyLb52bDHD2I1K0kjVukZvlPrATV17m7Jl6Gt5exRRicPMi-nnrxF2jv2cR-QHeZSFTzG3sXxSgk6D6roe5DxDVdc2VpAiZWwe_rc9hqI-notFA/s640/activiti-liferay-admin.png" width="640" /></a></div>
<br />
<div>
<br /></div>
<div>
<br /></div>
<div>
<h2>
Implementation details:</h2>
</div>
<div>
On the implementation side, most noteworthy changes which have been implemented in activiti-portlets module: <a href="https://github.com/tlipski/Activiti/tree/master/modules/activiti-portlets">https://github.com/tlipski/Activiti/tree/master/modules/activiti-portlets</a> are:</div>
</div>
<div>
<ul>
<li>IdentityProvider implementation utilizing Liferay API (need to work on user photos though!)</li>
<li>Spring-Vaadin bridging for portal (see my <a href="http://blog.tomeklipski.com/2013/02/integrating-spring-vaadin-liferay.html">previous post</a>).</li>
<li>Custom portlet applications and main Window class. The interesting detail here is that with Vaadin portlets, you should not set width of components to 100%, as it will result in 0px height. Generally speaking, the height in Vaadin portlet components should be set to undefined - null, or a preset value in pixels.</li>
</ul>
<h2>
Next steps?</h2>
</div>
<div>
Connecting Liferay with Activiti brings a myriad of new possibilities, but before that, some basic things need to be done:</div>
<div>
<ul>
<li>More portlets! Reporting and Processes tabs need their respective portlets, but also "start process" portlet might be worth considering since we can manage UI contents in a more flexible way.</li>
<li>More testing! If anyone is interested in testing Activiti portlets in Liferay, let me know and I will provide prebuilt applications. So far, I've used Liferay 6.1 GA2 CE and H2 in-memory database for Activiti.</li>
<li>Friendly urls - especially for tasks resolved by id, and for interportlet communication - e.g. when starting a process.</li>
</ul>
</div>
TLhttp://www.blogger.com/profile/02471354092762652778noreply@blogger.com6tag:blogger.com,1999:blog-9074762293446716009.post-46998238524845609182013-02-24T21:32:00.000+00:002013-03-15T19:52:13.692+00:00Integrating Spring, Vaadin & LiferayAs there are numerous materials on how to integrate Vaadin with Spring, Spring with Liferay (using portlet dispatcher) and Vaadin with Liferay, the integration of all three technologies at once is not documented well.<br />
In such approach, there are two major differences between Vaadin with Spring in a standard web application and portlets-based one. This post is based on Vaadin 6, but the rules itself apply
to Vaadin 7 as well.<br />
<h3>
The setup</h3>
The solution requires that we will overwrite two methods defined in <code class="prettyprint lang-java">com.vaadin.terminal.gwt.server.AbstractApplicationPortlet2</code>.<br />
To achieve that, we have to create our own subclass:<br />
<pre class="prettyprint lang-java">package com.tomeklipski.blog.sample.vaadin;
import com.vaadin.Application;
import com.vaadin.terminal.gwt.server.ApplicationPortlet2;
import javax.portlet.*;
import java.io.IOException;
/**
* Created with IntelliJ IDEA.
*
* @author tomek@lipski.net.pl
* Date: 2/23/13 4:06 PM
*/
public class MyApplicationPortlet extends ApplicationPortlet2 {
/* overwritten methods here */
}
</pre>
The newly created class has to be used in web.xml:<br />
<pre class="prettyprint lang-xml"><servlet>
<servlet-name>VaadinPortletServlet</servlet-name>
<servlet-class>com.liferay.portal.kernel.servlet.PortletServlet</servlet-class>
<init-param>
<param-name>portlet-class</param-name>
<param-value>com.tomeklipski.blog.sample.vaadin.MyApplicationPortlet</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
</pre>
And in portlet.xml as well:<br />
<pre class="prettyprint lang-xml"><portlet>
<portlet-name>MyPortlet</portlet-name>
<display-name>My Sample Portlet</display-name>
<portlet-class>com.tomeklipski.blog.sample.vaadin.MyApplicationPortlet</portlet-class>
<init-param>
<name>application</name>
<value>com.tomeklipski.sample.vaadin.SomeApplication</value>
</init-param>
<init-param>
<name>widgetset</name>
<value>com.vaadin.portal.gwt.PortalDefaultWidgetSet</value>
</init-param>
<supports>
<mime-type>text/html</mime-type>
<portlet-mode>view</portlet-mode>
</supports>
<portlet-info>
<title>My Sample Portlet</title>
<short-title>Sample</short-title>
</portlet-info>
</portlet>
</pre>
<h3>
Accessing Spring context in portlet</h3>
First of all - portlets are configured using listeners as well. Liferay adds its listeners to web.xml during deploy, so listeners in web.xml might look like this:
<br />
<pre class="prettyprint lang-xml"><listener>
<listener-class>
com.liferay.portal.kernel.servlet.PluginContextListener
</listener-class>
</listener>
<listener>
<listener-class>
com.liferay.portal.kernel.servlet.SerializableSessionAttributeListener
</listener-class>
</listener>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
<listener>
<listener-class>
com.liferay.portal.kernel.servlet.PortletContextListener
</listener-class>
</listener>
</pre>
<br />
As you can see, they are all mixed up and we cannot rely that during invocation of method <code class="prettyprint lang-java">javax.portlet.Portlet.init(PortletConfig config)</code> the spring context is available.<br />
If we want to refer to our application as a Spring bean, we have to overwrite method <code class="prettyprint lang-java">com.vaadin.terminal.gwt.server.AbstractApplicationPortlet.getNewApplication(PortletRequest request)</code>, and attempt to retrieve Spring context here:<br />
<pre class="prettyprint lang-java">@Override
protected Application getNewApplication(PortletRequest request) {
PortletContext portletContext = getPortletContext();
ApplicationContext webApplicationContext
= PortletApplicationContextUtils.getWebApplicationContext(portletContext);
Application app = (Application) webApplicationContext.getBean("app");
return app;
}
</pre>
The fetching of portlet and web application contexts can of course be optimized.<br />
<h3>
Accessing "session" scope</h3>
The "session" scope in Spring is a convient way to store objects related to a session, for example:<br />
<pre class="prettyprint lang-xml"><bean name="app" class="com.tomeklipski.sample.vaadin.SomeApplication" scope="session">
<property name="dataManager" ref="dataManager" />
<property name="i18nManager" ref="i18nManager" />
</bean>
</pre>
In standard web application, we can enable it using HttpRequestListener in web.xml:<br />
<pre class="prettyprint lang-xml"><listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
</pre>
But, in JSR286 portlet, the listener is not invoked as it should and portlet specification does not provide the ability to add portlet request listeners as above.<br />
Luckily, we can overwrite the method <code class="prettyprint lang-java">com.vaadin.terminal.gwt.server.AbstractApplicationPortlet.handleRequest(PortletRequest request, PortletResponse response)</code> with a code, that will set appropriate ThreadContext values:<br />
<pre class="prettyprint lang-java">@Override
protected void handleRequest(PortletRequest request,
PortletResponse response)
throws PortletException, IOException {
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContextHolder.setLocaleContext(
new SimpleLocaleContext(request.getLocale()), false);
// Expose current RequestAttributes to current thread.
RequestAttributes previousRequestAttributes = RequestContextHolder
.getRequestAttributes();
PortletRequestAttributes requestAttributes = null;
if (previousRequestAttributes == null ||
previousRequestAttributes.getClass()
.equals(PortletRequestAttributes.class)) {
requestAttributes = new PortletRequestAttributes(request);
RequestContextHolder.setRequestAttributes(requestAttributes,
false);
}
try {
super.handleRequest(request, response);
} finally {
LocaleContextHolder.setLocaleContext(previousLocaleContext,
false);
if (requestAttributes != null) {
RequestContextHolder.setRequestAttributes(
previousRequestAttributes, false);
requestAttributes.requestCompleted();
}
}
}
</pre>
<h3>
Summary</h3>
It is fairly easy to use Spring in Vaadin portlets - but there are some differences between standard Vaadin-Spring application approach and portletized one. We can easily overcome
them by overwriting two methods in <code class="prettyprint lang-java">com.vaadin.terminal.gwt.server.AbstractApplicationPortlet</code>.TLhttp://www.blogger.com/profile/02471354092762652778noreply@blogger.com1tag:blogger.com,1999:blog-9074762293446716009.post-25233096669553094302012-05-06T12:45:00.000+01:002013-02-24T19:54:10.630+00:00#Clojure , #MongoDB and removing a set of items<p>When using <a href="https://github.com/aboekhoff/congomongo" target="_blank">congomongo</a> library to access MongoDB in Clojure, it is sometimes required to remove a collection of items. As the example on GitHub page shows only how to remove one item, I have pasted the code here.</p>
<p><strong>Setup test data</strong></p>
<p>To setup test data, we would use 100 items in :points collection.</p>
<pre class="prettyprint lang-clj">
(use 'somnium.congomongo)
(mass-insert! :points
(for [x (range 0 10) y (range 0 10)] {:x x :y y}))
(count (fetch :points))
;=> 100
</pre>
<p><em>Please note, that I am not writing any code specific to establishing a connection here. For more information on using congomongo library, please visit <a href="https://github.com/aboekhoff/congomongo" target="_blank">congomongo github site</a>.</em></p>
<p><strong>The obvious way</strong></p>
<p>The most obvious way to do so, would be to pass a list objects to <em>destroy!</em> function. Sadly, it won't work:</p>
<pre class="prettyprint lang-clj">
user=> (destroy! :points (fetch :points))
;=> ClassCastException clojure.lang.LazySeq cannot be
;=> cast to com.mongodb.DBObject
;=> somnium.congomongo/destroy! (congomongo.clj:419)
</pre>
<p><strong>Using for loop</strong></p>
<p>We can also invoke <code>destroy!</code> a hundred times:</p>
<pre class="prettyprint lang-clj">
(count (fetch :points))
;=> 100
(for [p (fetch :points)]
(destroy! :points p))
(count (fetch :points))
;=> 0
</pre>
<p>It will work, but it is not optimal.</p>
<p><strong>Using where clause</strong></p>
<p>The best way to remove a collection of items in MongoDB using congomongo, it would be to use where clause for a <code>destroy!</code> function:</p>
<pre class="prettyprint lang-clj">
(count (fetch :points))
;=> 100
(destroy! :points {:x {:$gt 4}})
(count (fetch :points))
;=> 50
</pre>
<p>It is also possible to refer MongoDB items by their ids in where clause:</p>
<pre class="prettyprint lang-clj">
(count (fetch :points))
;=> 100
(destroy! :points {:_id {:$in (map :_id (fetch :points))}})
(count (fetch :points))
;=> 0
</pre>
<script type="text/javascript">
prettyPrint();
</script>TLhttp://www.blogger.com/profile/02471354092762652778noreply@blogger.com0tag:blogger.com,1999:blog-9074762293446716009.post-137830735680032312012-04-23T23:14:00.000+01:002013-02-24T19:42:38.678+00:00Hacking Activiti BPM engine: how to use custom MyBatis queries<p><span style="font-size: large;">The issue</span></p>
<p>One big change that Activiti has introduced over jBPMv4 was to forego <a title="http://www.hibernate.org" href="http://www.hibernate.org" target="_blank">Hibernate</a> ORM and use <a title="http://www.mybatis.org/" href="http://www.mybatis.org/" target="_blank">MyBatis</a>. MyBatis is simpler and cleaner to use, but - it does not provide simple interface to perform queries that are not defined in annotations or XML files.</p>
<p>And even though Activiti provides great query interface, so you can do things like this:</p>
<pre class="prettyprint lang-java">
List<Task> tasks = getProcessEngine().getTaskService().createTaskQuery()
.taskName(taskName)
.executionId(taskExecutionId)
.taskAssignee(user.getLogin())
.listPage(0, 1);
</pre>
<p>with relative ease, there are still some limitations to the API provided by Activiti Service classes (e.g. you cannot add IN clause for task assignees).</p>
<p><span style="font-size: large;">Possible solutions</span></p>
<p>One thing that you can do, is to just skip Activiti interface, and access database directly. Surely it will work, but such approach has many downsides - you will be fetching information about Activiti processes in two different ways, you will have to map data to Activiti objects yourself, and so on.</p>
<p>What we have managed to do for Aperte Workflow <-> Activiti integration (in version 2.0, where API is a bit more demanding), is indeed a hack. It is not as elegant as constructing query for jBPM, but still is quite simple and manageable. It propably can be done with Spring in a similiar fashion, or with SelectBuilder - but the principle is similiar.</p>
<p><span style="font-size: large;">The hack</span></p>
<p><strong> 1. Enhance Activiti's MyBatis mapping file - by adding our own version in a different package.</strong></p>
<p>The enhanced file looks almost like the original - with one exception:</p>
<pre class="prettyprint lang-xml">
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="lazyLoadingEnabled" value="false" />
</settings>
<mappers>
<mapper resource="org/activiti/db/mapping/entity/Attachment.xml" />
<mapper resource="org/activiti/db/mapping/entity/Comment.xml" />
<mapper resource="org/activiti/db/mapping/entity/Deployment.xml" />
<mapper resource="org/activiti/db/mapping/entity/Execution.xml" />
<mapper resource="org/activiti/db/mapping/entity/Group.xml" />
<mapper resource="org/activiti/db/mapping/entity/HistoricActivityInstance.xml" />
<mapper resource="org/activiti/db/mapping/entity/HistoricDetail.xml" />
<mapper resource="org/activiti/db/mapping/entity/HistoricProcessInstance.xml" />
<mapper resource="org/activiti/db/mapping/entity/HistoricTaskInstance.xml" />
<mapper resource="org/activiti/db/mapping/entity/IdentityInfo.xml" />
<mapper resource="org/activiti/db/mapping/entity/IdentityLink.xml" />
<mapper resource="org/activiti/db/mapping/entity/Job.xml" />
<mapper resource="org/activiti/db/mapping/entity/Membership.xml" />
<mapper resource="org/activiti/db/mapping/entity/ProcessDefinition.xml" />
<mapper resource="org/activiti/db/mapping/entity/Property.xml" />
<mapper resource="org/activiti/db/mapping/entity/Resource.xml" />
<mapper resource="org/activiti/db/mapping/entity/TableData.xml" />
<mapper resource="org/activiti/db/mapping/entity/Task.xml" />
<mapper resource="org/activiti/db/mapping/entity/User.xml" />
<mapper resource="org/activiti/db/mapping/entity/VariableInstance.xml" />
<mapper resource="org/aperteworkflow/ext/activiti/mybatis/Task-enhanced.xml" />
</mappers>
</configuration>
</pre>
<p>And the addition is:</p>
<pre class="prettyprint lang-xml">
<mapper resource="org/aperteworkflow/ext/activiti/mybatis/Task-enhanced.xml" />
</pre>
<p><strong>2. Create enhanced mapper resource</strong></p>
<p>This mapper resource add additional query conditions to the initial task query. The beginning of it is the same as original, but at the end we have added support for in clauses on several fields:</p>
<pre class="prettyprint lang-xml">
<!-- new conditions -->
<if test="owners != null && owners.size() > 0">
and T.OWNER_ IN
<foreach item="owner" index="index" collection="owners"
open="(" separator="," close=")">
#{owner}
</foreach>
</if>
<if test="notOwners != null && notOwners.size() > 0">
and T.OWNER_ NOT IN
<foreach item="owner" index="index" collection="notOwners"
open="(" separator="," close=")">
#{owner}
</foreach>
</if>
<if test="groups != null && groups.size() > 0">
and T.ASSIGNEE_ is null
and I.TYPE_ = 'candidate'
and I.GROUP_ID_ IN
<foreach item="group" index="index" collection="groups"
open="(" separator="," close=")">
#{group}
</foreach>
</if>
<if test="taskNames != null && taskNames.size() > 0">
and T.NAME_ IN
<foreach item="name" index="index" collection="taskNames"
open="(" separator="," close=")">
#{name}
</foreach>
</if>
</foreach>
</where>
</sql>
</pre>
<p>The entire mapper file can be downloaded here from Aperte Workflow's github repository - <a href="https://github.com/bluesoft-rnd/aperte-workflow-core/blob/merge_A/core/activiti-context/src/main/resources/org/aperteworkflow/ext/activiti/mybatis/Task-enhanced.xml" target="_blank">core/activiti-context/src/main/resources/org/aperteworkflow/ext/activiti/mybatis/Task-enhanced.xml</a>.</p>
<p><strong>3. Introduce new configuration to MyBatis</strong></p>
<p>As MyBatis runs inside of Activiti internals, we have to access and alter Activiti configuration mechanisms. To do that, we simply override one of the methods with our copy (which points to a new mapping file):</p>
<pre class="prettyprint lang-java">
public class CustomStandaloneProcessEngineConfiguration
extends StandaloneProcessEngineConfiguration {
@Override
protected void initSqlSessionFactory() {
if (sqlSessionFactory == null) {
InputStream inputStream = null;
try {
inputStream = ReflectUtil.getResourceAsStream(
"org/aperteworkflow/ext/activiti/mybatis/mappings-enhanced.xml");
// update the jdbc parameters to the configured ones...
Environment environment = new Environment("default", transactionFactory,
dataSource);
Reader reader = new InputStreamReader(inputStream);
XMLConfigBuilder parser = new XMLConfigBuilder(reader);
Configuration configuration = parser.getConfiguration();
configuration.setEnvironment(environment);
configuration.getTypeHandlerRegistry().register(VariableType.class,
JdbcType.VARCHAR,
new IbatisVariableTypeHandler());
configuration = parser.parse();
sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
} catch (Exception e) {
throw new ActivitiException(
"Error while building ibatis SqlSessionFactory: " + e.getMessage(), e);
} finally {
IoUtil.closeSilently(inputStream);
}
}
}
}
</pre>
<p>You can of course do lots of amazing customizations here. And use this new class for Activiti BPM engine initialization:</p>
<pre class="prettyprint lang-java">
CustomStandaloneProcessEngineConfiguration customStandaloneProcessEngineConfiguration =
new CustomStandaloneProcessEngineConfiguration();
customStandaloneProcessEngineConfiguration
.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE)
.setDataSource(getDataSourceWrapper(sess))
.setHistory(ProcessEngineConfiguration.HISTORY_FULL)
.setTransactionsExternallyManaged(true);
</pre>
<p><strong>4. Enhance query object</strong></p>
<p>Activiti uses wonderful, clean and simple query objects, that provide structure to MyBatis query parameters. To introduce new parameters, all we have to do, is to enhance TaskQueryImpl class with our own:</p>
<pre class="prettyprint lang-java">
package org.aperteworkflow.ext.activiti.mybatis;
import org.activiti.engine.impl.TaskQueryImpl;
import java.util.HashSet;
import java.util.Set;
/**
* @author tlipski@bluesoft.net.pl
*/
public class TaskQueryImplEnhanced extends TaskQueryImpl {
private Set<String> creators = new HashSet<String>();
private Set<String> owners = new HashSet<String>();
private Set<String> groups = new HashSet<String>();
private Set<String> notOwners = new HashSet<String>();
private Set<String> taskNames = new HashSet<String>();
public Set<String> getGroups() {
return groups;
}
public Set<String> getNotOwners() {
return notOwners;
}
public Set<String> getOwners() {
return owners;
}
public Set<String> getCreators() {
return owners;
}
public Set<String> getTaskNames() {
return taskNames;
}
public TaskQueryImplEnhanced addTaskName(String name) {
taskNames.add(name);
return this;
}
public TaskQueryImplEnhanced addOwner(String login) {
owners.add(login);
return this;
}
public TaskQueryImplEnhanced addGroup(String name) {
groups.add(name);
return this;
}
public TaskQueryImplEnhanced addNotOwner(String login) {
notOwners.add(login);
return this;
}
public TaskQueryImplEnhanced addCreator(String login) {
creators.add(login);
return this;
}
}
</pre>
<p><strong>5. Invoke new query</strong></p>
<p>And finally, we can invoke our new query with multiple assignees and other custom where clauses:</p>
<pre class="prettyprint lang-java">
final TaskQueryImplEnhanced q = new TaskQueryImplEnhanced();
for (UserData u : filter.getOwners()) {
q.addOwner(u.getLogin());
}
for (UserData u : filter.getCreators()) {
q.addCreator(u.getLogin());
}
for (UserData u : filter.getNotOwners()) {
q.addNotOwner(u.getLogin());
}
for (String qn : filter.getQueues()) {
q.addGroup(qn);
}
ActivitiContextFactoryImpl.CustomStandaloneProcessEngineConfiguration
processEngineConfiguration = getProcessEngineConfiguration();
CommandExecutor commandExecutorTxRequired = processEngineConfiguration
.getCommandExecutorTxRequired();
List<Task> tasks = commandExecutorTxRequired.execute(
new Command<List<Task>>() {
@Override
public List<Task> execute(CommandContext commandContext) {
return commandContext.getDbSqlSession()
.selectList("selectTaskByQueryCriteria_Enhanced", q);
}
});
</pre>
<p><em>Please note, that we are also exposing CommandExecutor object instance to access DbSqlSession.</em></p>
<p><span style="font-size: large;">Summary</span></p>
<p>The technique presented here would be unnecessary or much simpler if Activiti would provide external means to configure MyBatis. Maybe that is a thing, that will be available in the future versions of Activiti.</p>
<p>Still, even at this moment, it is fairly easy to enhance/alter Activiti's behaviour. I haven't seen any final classes (a common sight in Hibernate - e.g. org.hibernate.impl.SessionImpl) and the internals of Activiti are quite simple to understand.</p>
<script>
prettyPrint();
</script>TLhttp://www.blogger.com/profile/02471354092762652778noreply@blogger.com3tag:blogger.com,1999:blog-9074762293446716009.post-62970615289661094782012-04-23T13:46:00.000+01:002013-03-15T19:52:34.100+00:00Why Vaadin is different - and how can it make your web application great<em>on the example of <a href="http://www.aperteworkflow.org/" target="_blank" title="Aperte Workflow - Open Source BPMS">Aperte Workflow</a></em><br />
<span style="font-size: large;">Vaadin</span><br />
<strong><a href="http://vaadin.com/" target="_blank" title="Vaadin">Vaadin</a> is an Open Source widget-oriented, Java Rich Internet Application web framework, which allows programmers to develop browser-based user interface entirely in Java - server-side. </strong><br />
That's it. This is what makes Vaadin different and this is what is game-changing in comparison to almost any other popular web framework.<br />
<h3>
100% JVM</h3>
Because everything is written in Java and runs server-side, you can do things, that were almost impossible or very painful for web frameworks before:<br />
<ul>
<li>Have one, coherent application, with its logic seperated according to your needs - not to what the framework designers had in mind</li>
<li>Apply Java programming patterns (compile-time polymorphism, inheritance, etc.) to all of your code - and build your application from coherent modules using these patterns.</li>
<li>Write app in a scripting language of your choice - in Scala, Clojure, Common Lisp, Groovy - anything that runs on JVM as a scripting language</li>
<li>Debug user interface logic using standard tools - not just in a sandbox mode</li>
<li>Introduce plugins to your application using OSGi framework</li>
<li>Develop rapidly, by reloading your code in mere seconds - e.g. using JRebel agent or even OSGi framework</li>
</ul>
Vaadin still allows programmers to access the browser side - but in a controlled way, using <a href="http://vaadin.com/directory" target="_blank" title="Vaadin Add-ons">Add-ons</a> - Vaadin Add-on Directory provides ecosystem for those. And for most applications - you can easily build them using just the standard ones, provided by Vaadin out-of-the-box.<br />
<h3>
...and more</h3>
JVM itself would be enough, but Vaadin is not only great in the idea itself, but also in implementation. The applications have eye-friendly look out of the box, you can also apply custom CSS and themes easily.<br />
The UI is well-optimized even in comparison to client-side only frameworks - and it works on all modern browsers out of the box (IE6/7 - with some downsides too!).<br />
There is even mobile device support with <a href="https://vaadin.com/directory#addon/vaadin-touchkit" title="https://vaadin.com/directory#addon/vaadin-touchkit">Vaadin TouchKit</a>, Vaadin also supports JSR168/286 portals (e.g. Liferay Portal) and even Google App Engine.<br />
<h3>
Vaadin & Aperte Workflow</h3>
Aperte Workflow is an Open Source BPMS - the application that eventually is built during the introduction on BPM-class system to an enterprise. Aperte Workflow is designed with strong emphasis on modularity, re-usabiliti and extensibility of solutions created.<br />
User interface in Aperte Workflow is built on Vaadin. The approach for BPM task UI concept in Aperte Workflow is that for each human task, user interface is built from reusable blocks or components (in Aperte Workflow terminology: widgets). Such components can be process-aware (e.g. display process instance data) or just organize other components.<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxP8Wjg6iD4k3pxqdj8jHiahW6DX6W6eJYC6wABLnThTyREh-PWxYZUIz2MT27fA77HIvGjRenrIpSWsadIcO7xqFQII3EWOvmPJliwwhZXOlHUe_8cYqOcvNMsZxsHXLfqZTdE_B9ioA/s1600/widgets.png.scaled1000.png" imageanchor="1">
<img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxP8Wjg6iD4k3pxqdj8jHiahW6DX6W6eJYC6wABLnThTyREh-PWxYZUIz2MT27fA77HIvGjRenrIpSWsadIcO7xqFQII3EWOvmPJliwwhZXOlHUe_8cYqOcvNMsZxsHXLfqZTdE_B9ioA/s320/widgets.png.scaled1000.png" /></a>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaeALKFyegToFKJDKKBeLjEj8zXpSLqL3os0PhUrWIrTjaNMa7Kojhue_tISIqgQwzmiAiVU9v9Qn3sh_6rARCmjqlAJHz8TXWwCLGGxKntrUcmmnl0GdC9_UJjNQSOAaZLSBKEZJEw-g/s1600/widgets2.png.scaled1000.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaeALKFyegToFKJDKKBeLjEj8zXpSLqL3os0PhUrWIrTjaNMa7Kojhue_tISIqgQwzmiAiVU9v9Qn3sh_6rARCmjqlAJHz8TXWwCLGGxKntrUcmmnl0GdC9_UJjNQSOAaZLSBKEZJEw-g/s320/widgets2.png.scaled1000.png" /></a><br />
With Vaadin, creating these components is a blast, even for someone new to the concept. You just code your component in Java - implementing some interfaces or extending ready-to-use classes. Vaadin widgets are there - so, for example - you don't have to worry on how to implement your own or incorporate existing suggesting combo box or grid - they are already here, well designed, optimized to work together with other combo boxes.<br />
After coding - you just deploy/update them to a plugin mechanism. Being written entirely in Java, we can manipulate them in our plugins very easily. We don't have to build a set of JavaScripts to provide RIA experience to the users - but neither we have to refresh web page after every user action.<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDs3hXcAYfdlNrPVYricIf0C-2emzXPBwfzyvqfFXRrQjKP2tUUwqPL-5w4svdew-JNs-eifzjbm5mRqH5a5lC411s4W38Maiz0pE0OWVhC-ZjRV0I8cANH15o2xrZnv4MYAcUkKbnVl4/s1600/Aperte_Workflow_Plugin_Manager_-_Aperte_Workflow_Demo-141920.png.scaled1000.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDs3hXcAYfdlNrPVYricIf0C-2emzXPBwfzyvqfFXRrQjKP2tUUwqPL-5w4svdew-JNs-eifzjbm5mRqH5a5lC411s4W38Maiz0pE0OWVhC-ZjRV0I8cANH15o2xrZnv4MYAcUkKbnVl4/s320/Aperte_Workflow_Plugin_Manager_-_Aperte_Workflow_Demo-141920.png.scaled1000.png" /></a><br />
So the code->deploy->test->code->deploy->... cycle is very short - and still we write our application in plain old Java. I wouldn't even attempt to try to implement such thing with pure GWT or JSF.<br />
Plugin mechanism provides integration with an Aperte Workflow web-based modeler - which is based on Signavio core, but more complex elements specific for Aperte Workflow were written in Vaadin too.<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEim4WdiyNq2PpVvt9X73QWBBrSSMkHH9H4TAVci6k-BdGGNvUvFWHDq04QaDaMfesYy_JD4QsktjGI-k8QsJXKK06kUFVWWsECIMs61K2vJl_Nb24Ejjktw9nmIx8uONHzdqqdt0As0xBw/s1600/Flight_Reservation_-_Signavio-142306.png.scaled1000.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEim4WdiyNq2PpVvt9X73QWBBrSSMkHH9H4TAVci6k-BdGGNvUvFWHDq04QaDaMfesYy_JD4QsktjGI-k8QsJXKK06kUFVWWsECIMs61K2vJl_Nb24Ejjktw9nmIx8uONHzdqqdt0As0xBw/s320/Flight_Reservation_-_Signavio-142306.png.scaled1000.png" /></a><br />
Use of Vaadin in modeler allowed us also to engineer preview mechanics to some components (more to come) - for example, Advanced Process Data widget, which provides generic-form feature (it's just one plugin, not an obligation or part of the Aperte Workflow core - you can use, you can replace it, or you can use other implementation, or just go with your own plugins) generates preview in real-time. And the preview is the same code and configuration that will be run in the real user interface for a real BPM task.<br />
What else Vaadin has allowed us to do with relative ease, is that Advanced Process Data Widget provides JSR223 scripting support - for validation and manipulation of the configured form.<br />
<h3>
Summary</h3>
In Aperte Workflow, we have chosen Vaadin as our UI framework, not only to save ourselves from spending too much time coding the user interface. We have chosen it mainly because with Vaadin, it is possible to create much more modular RIA web application than with any other framework we have seen.<br />
Will Aperte Workflow be possible without Vaadin. Sure - but with much bigger effort and budget. And the result would be underwhelming without Vaadin - or we would have created our own, imperfect implementation of Vaadin.<br />
<span style="text-decoration: underline;">More information:</span><br />
<ul>
<li><a href="http://vaadin.com/" target="_blank">Vaadin website</a></li>
<li><a href="https://vaadin.com/book" target="_blank" title="https://vaadin.com/book">Book of Vaadin</a> (there is even an ebook version for Kindle, iPad, Android, etc.)</li>
<li><a href="http://demo.vaadin.com/sampler/" target="_blank" title="http://demo.vaadin.com/sampler/">Vaadin widgets gallery</a></li>
<li><a href="http://vaadin.com/directory" target="_blank" title="http://vaadin.com/directory">Vaadin Add-ons directory</a></li>
<li><a href="http://www.aperteworkflow.org/" target="_blank" title="http://www.aperteworkflow.org">Aperte Workflow</a></li>
</ul>
TLhttp://www.blogger.com/profile/02471354092762652778noreply@blogger.com1tag:blogger.com,1999:blog-9074762293446716009.post-42044063450102843092012-04-18T14:00:00.000+01:002013-02-24T18:52:47.079+00:00Aperte Workflow - why another BPMS?<p><span style="font-size: large;">What is Aperte Workflow?</span></p>
<p><a title="Aperte Workflow" href="http://www.aperteworkflow.org" target="_blank">Aperte Workflow</a> is a BPMS (Business Process Management Suite) designed and developed by BlueSoft. The project has started in 2011 and is currently approaching version 2.0.</p>
<p>BlueSoft is an Independent Software Vendor specializing in custom, tailor-made IT solutions for the enterprise customers - based either on Open Source or proprietary software.</p>
<p><span style="font-size: small;">The company itself comes with over 9 years of experience in developing such solutions, which gives a certain perspective on how things should be done. But still, young enough to be able to innovate effectively.</span></p>
<p><span style="font-size: large;">Why build another Open Source BPM solution?</span></p>
<p>That is a good question, and we ask it ourselves every time we forego existing, available technology and start creating something new. </p>
<p>With Open Source BPMs and Aperte Workflow, the decision came from a simple observation: that every time you need to deploy a BPM-based solution (be it Open Source or commercial software) in a real application, which provides real processes, used by real users, you need to build an application around it.</p>
<p>You have to provide at least:</p>
<ul>
<li>Integration with Customer's authentication/authorization mechanisms.</li>
<li>Custom User Interface. Forms with text, number and date field are great, but usually the business requirements are much more complicated. </li>
<li>Integration on the browser-level with Customer's intranet portal (desktop based BPMS in my opinion are a total misunderstanding) - or some kind of base for BPM application.</li>
<li>Enterprise integration with the Customer's other IT systems (CRM, ERP, ...).</li>
<li>Flexible data model</li>
</ul>
<p>And yes, it is possible to build such solution yourself. Integrate existing technologies into one solution, learn how to use them, find bugs in your code and in the code of the technologies you are using - and then, finally you can start developing your processes. </p>
<p>This is exactly what Aperte Workflow provides - a ready-to-use, well-tested and mature product, which provides a platform or a placeholder for your processes and custom mechanisms. Done right, with reusability, scalability and performance in mind.</p>
<p>We do not aim to it be a complete solution for your business process (although it is very easy to build one with Aperte Workflow!) - we understand, that every process in every company can be a little different. But, there are some things that have to be done for every BPM deployment, so why not do them once and right?</p>
<p>For us, Aperte Workflow is different from the other solutions, as it approaches problems on a different level. We do not try to provide an answer to every problem that rises during BPM deployment in an organization - but rather to provide means, that the solution will be worked out easily and will be coherent with other parts of the deployment and will be re-usable in other areas.</p>
<p><span style="font-size: large;">What Aperte Workflow provides?</span></p>
<p>Aperte Workflow provides common things that usually have to be built when deploying BPM in an organization. But, since this is a standalone product, even more features, that wouldn't be usually implemented in a custom BPM deployment are provided.</p>
<p><span style="font-size: medium;">Portal Integration</span></p>
<p>Aperte Workflow integrates with JSR168/286 compliant portals (e.g. Liferay). By doing so, you do not only have an awesome integration with existing portal or enterprise IT infrastructure (e.g. Active Directory, NTLM, ...), but you also have a place to introduce and integrate other applications.</p>
<p>Using JSR168/286 allows you to standarize and unify all your browser-based applications - into a one, coherent intranet portal. </p>
<p><span style="font-size: medium;">Custom User Interfaces</span></p>
<p>With Aperte Workflow, user interface for each human task (on the task list or in details) is built from re-usable components. These components, provided as plugins, can provide:</p>
<ul>
<li>all-round, generic features, such as simple input form or a tabsheet organizing other components</li>
<li>specific features - usable in most of the processes, such as comment form or process history log</li>
<li>custom business logic - related to a certain process or organization, a CRM customer search form would be an example for that.</li>
</ul>
<p>The UI is provided by <a title="http://vaadin.com" href="http://vaadin.com" target="_blank">Vaadin</a>, therefore its customization requires no HTML/CSS skills - it is programmed directly in Java. And with <a title="http://vaadin.com" href="http://vaadin.com" target="_blank">Vaadin</a>, we can build beautiful, rich web interfaces with ease.</p>
<p><span style="font-size: medium;">Enterprise integration</span></p>
<p>Aperte Workflow allows users to build automated steps as plugins. As with User Interface, some of them can be quite generic (e.g. invoke Drools), others - very specific. All of them can be reused in different processes without any trouble.</p>
<p>Also, Aperte Workflow comes with a sample plugin, that embeddes Mule ESB as an OSGi service. Other plugins can deploy simple Mule services, and processes can invoke them using Mule automated step. But - this is just a convienent sample of integration mechanisms and if for example ServiceMix would to be embedded as an integrated ESB - why not?</p>
<p><span style="font-size: medium;">Extensible data model </span></p>
<p>Aperte Workflow's data model is built with JPA/Hibernate. That grants us significant portability with little effort, but also allows the plugins to provide their own data model without performance downgrade - the data model is mapped directly to SQL database. </p>
<p><span style="font-size: medium;">BPMN2.0 modeler</span></p>
<p>When designing BPM system from scratch, around existing BPM engine or solution, you usually have to configure process in two places - one for the BPM engine and the other for your customizations. Rarely you would take the effort to create a graphical, coherent tool to do that. </p>
<p>But, as Aperte Workflow is designed for more than one deployment, such effort pays off. That's why we have taken <a title="http://code.google.com/p/signavio-core-components/" href="http://code.google.com/p/signavio-core-components/" target="_blank">Signavio Core Components</a> BPMN2.0 editor and combined it with our configuration controls into one, complete, browser-based process modeler.</p>
<p><span style="font-size: large;">How Aperte Workflow is built?</span></p>
<p>We have decided to build Aperte Workflow on top of existing, Open Source technologies. That not only saves us from the trouble of building everything from scratch, but also provides other, even more significant advantages:</p>
<ul>
<li><strong>Maturity from the start.</strong> We don't have to test, mature and grow our components. They are already checked by thousands of their users and built around their feedback.</li>
<li><strong>Much easier to learn.</strong> It is much easier to start using or diagnose a problem with a solution built from technologies you are already familiar with, or that have a thriving community around them.</li>
<li><strong>New features.</strong> If you are using your own software, there is a great chance, that all the features have to analyzed, provided, tested and checked by you. </li>
</ul>
<p>Aperte Workflow is built from many Open Source libraries and frameworks, to mention only the most significant:</p>
<ul>
<li><a title="http://www.liferay.com" href="http://www.liferay.com" target="_blank">Liferay Portal</a> as a default JSR168/286 container</li>
<li><a title="http://www.jboss.org/jbpm" href="http://www.jboss.org/jbpm" target="_blank">jBPM</a> and <a title="http://www.activiti.org" href="http://www.activiti.org" target="_blank">Activiti</a> BPM engines - it is also possible to user other implementations</li>
<li><a title="http://vaadin.com" href="http://vaadin.com" target="_blank">Vaadin</a> as a default framework for building user interfaces</li>
<li><a title="http://felix.apache.org/" href="http://felix.apache.org/" target="_blank">Apache Felix</a> - an OSGi framework</li>
<li><a title="http://www.hibernate.org/" href="http://www.hibernate.org/" target="_blank">Hibernate</a> - ORM/persistence</li>
<li><a title="http://www.mulesoft.org/" href="http://www.mulesoft.org/" target="_blank">Mule ESB</a> - as a sample embedded ESB provider</li>
</ul>
<p><span style="font-size: large;">Create once, use many times</span></p>
<p>Aperte Workflow is great when it comes to building reusable components. Yes, many processes may differ, but building blocks for them can be re-used many times. Some of them, such as simple form displaying/editing process attributes or comment input form can be universal across the whole world, other - can be universal only in a specific organization - for example CRM customer search form would be applicable to many processes but only in current organization.</p>
<p>As Aperte Workflow is created in Java(tm) technology, the natural and simplest way to provide plugin mechanisms is to use an <a title="OSGi" href="http://www.osgi.org" target="_blank">OSGi</a> framework. OSGi is great, because it provides a standarized, well-thought way to:</p>
<ul>
<li>isolate each plugin within its classloader space - with its own libraries and dependencies</li>
<li>provide a means for plugins to communicate with each other</li>
<li>manage dependencies between plugins</li>
</ul>
<p>With Aperte Workflow, reusability is provided on many levels, in compliance with the spirit of Service Oriented Architecture. Aperte Workflow services (bundled in OSGi plugins) can provide for example:</p>
<ul>
<li>user interface components</li>
<li>automated steps performing business logic or integration</li>
<li>localization (I18N)</li>
<li>custom mechanisms that access Aperte Workflow API - e.g. start processes or provide embedded ESB for processes</li>
<li>process dictionaries</li>
</ul>
<p>and many more..</p>TLhttp://www.blogger.com/profile/02471354092762652778noreply@blogger.com4