Skip to content

saidone75/weller

Repository files navigation

Weller

Weller is like Alfresco out-of-process extensions but in Clojure.

Clojars Version cljdoc badge

Outline

  • Lean and readable code
  • 100% pure Clojure
  • 100% test coverage
  • Benefits from programming at the REPL

Usage

Create filters by composing predicates

Filters are used for selecting messages that will flow through a tap. Filters can discriminate a message by matching several conditions (e.g. a node that has been created or deleted, an aspect that has been added to a node, a property that changed its value, etc.).

Filters can be made of a single predicate:

(pred/event? events/node-updated)

or by a composition of predicates using every-pred (logical AND) or some-fn (logical OR) Clojure built-in functions.

E.g. this matches every updated node which is also file:

(every-pred (pred/event? events/node-updated)
            (pred/is-file?))

this one matches every node with cm:titled or cm:dublincore aspects (regardless of the event type):

(some-fn (pred/node-aspect? cm/asp-titled)
         (pred/node-aspect? cm/asp-dublincore))

and this one matches updated nodes with cm:titled or cm:dublincore aspects:

(every-pred (pred/event? events/node-updated)
            (some-fn (pred/node-aspect? cm/asp-titled)
                     (pred/node-aspect? cm/asp-dublincore)))

The built-in predicates are available here while the events here.

Create functions for message processing

Functions are the pieces of code deputed to take the message and do something with it. The resource is a map representing a node in Alfresco.

A function that prints the node name could be as simple as:

(defn print-node-name
  [resource]
  (println (:name resource)))

a more useful function that makes use of the CRAL library to update the node on Alfresco:

(defn add-aspect
  [resource]
  (let [aspect-names (get-in (nodes/get-node (c/ticket) (:id resource)) [:body :entry :aspect-names])]
    (->> (model/map->UpdateNodeBody {:aspect-names (conj aspect-names cm/asp-dublincore)})
         (nodes/update-node (c/ticket) (:id resource)))))

Note that the (resource part of) message is automatically converted to a plain Clojure map with keys changed to kebab-case and keywordized thus looks like this (representing a node in this case):

{:primary-assoc-qname "cm:49d3a98c-6a2d-4851-a3a5-6de719033b90",
 :properties
 {:cm:auto-version true,
  :cm:version-type "MAJOR",
  :cm:auto-version-on-update-props false,
  :cm:version-label "1.0",
  :cm:initial-version true},
 :secondary-parents [],
 :is-file true,
 :is-folder false,
 :created-by-user {:id "admin", :display-name "Administrator"},
 :content
 {:mime-type "application/octet-stream",
  :size-in-bytes 0,
  :encoding "UTF-8"},
 :primary-hierarchy
 ["71290ab7-c07d-424e-a8b4-2eb3aefdd434"
  "7cf72ac7-addc-4fe5-8af2-26d50dc9d575"
  "81482fa8-3079-4187-96ae-43e36353d2c1"],
 :name "49d3a98c-6a2d-4851-a3a5-6de719033b90",
 :modified-at "2024-05-17T12:25:06.629Z",
 :node-type "cm:content",
 :id "8b799b25-6e1e-4ec9-92ad-7db2d6e5598e",
 :modified-by-user {:id "admin", :display-name "Administrator"},
 :aspect-names ["cm:versionable" "cm:auditable"],
 :@type "NodeResource",
 :created-at "2024-05-17T12:25:06.567Z"}

Configuration

Configuration is stored internally in the atom weller.config/config. It contains a map like this:

{:alfresco {:scheme         "http"
            :host           "localhost"
            :port           8080
            :core-path      "alfresco/api/-default-/public/alfresco/versions/1"
            :search-path    "alfresco/api/-default-/public/search/versions/1"
            :auth-path      "alfresco/api/-default-/public/authentication/versions/1"
            :discovery-path "alfresco/api/discovery"
            :user           "admin"
            :password       "admin"}
 :activemq {:scheme "tcp"
            :host   "localhost"
            :port   61616
            :topic  "alfresco.repo.event2"}}

When a message pipe is started then weller.config/configure is called and will try to load these files (in order):

(def ^:private cfg-files
  ["resources/weller.edn"
   "~/.weller/weller.edn"
   "./weller.edn"])

each (map contained in) file is (deep) merged with the previous, and values being overwritten by the most recent. Placeholders for environment variables can be used, e.g.:

{:alfresco {:user     "${ALF_USER:admin}"
            :password "${ALF_PASS:admin}"}}

will look for ALF_USER and ALF_PASS environment variables if defined, otherwise the default value will be used. Environment variables will win over the configuration files.

Lastly cral.config/configure is called and an Alfresco ticket is requested and then stored in :alfresco :ticket section of the map. It can be retrieved with (c/ticket) (assuming that config has been imported as [weller.config :as c]).

Build and start a message pipe

The standard constructor make-pipe will create a pipe that receives ActiveMQ messages. Then at least one tap must be connected using the function add-filtered-tap that takes a filter and a (processing) function as arguments (note that in Clojure functions are first-class and can be passed-to or returned-from other functions). Finally the pipe can be started manually by calling component/start on it.

(-> (pipe/make-pipe)
    (pipe/add-filtered-tap (pred/event? events/node-created) process-created-node)
    (pipe/add-filtered-tap (pred/event? events/node-deleted) process-deleted-node)
    (component/start))

The quick constructor takes directly a filter and a function. A tap will be created and connected internally and the pipe started automatically:

(pipe/make-pipe (pred/event? events/node-created) process-created-node)

License

Copyright (c) 2024 Saidone

Distributed under the GNU General Public License v3.0

About

Like Alfresco out-of-process extensions but in Clojure

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published