Adding Custom Build Steps

Geert edited this page Jun 8, 2017 · 9 revisions

The Roxy Deployer does a lot of things for you, but sometimes you'll need to do something that the Deployer doesn't handle yet. In that case, you can add a custom step to the deployer, specifying your own XQuery code.

The key is to use the deploy/app_specific.rb file. (You don't need to know Ruby to use this feature.) In this example, a user is counting the documents in the content database:

class ServerConfig
  def get_document_count()
    r = execute_query %Q{
      xdmp:estimate(fn:doc())
    }
    @logger.info(r.body)
  end
end

How To Use It

Custom build steps are implemented using Ruby functions. You can use these steps from the ml script, using the function names:

ml local create_view

Note that these functions are used with an environment.

How It Works

There are three key pieces you need to know to use this feature.

Defining a Function

Before you start editing, the deploy/app_specific.rb file looks like this:

class ServerConfig
  def my_custom_method()
    @logger.info(@properties["ml.content-db"])
  end
end

This is Ruby code. If you don't know Ruby, don't worry, you don't need to. The "class ServerConfig ... end" part of the code is a wrapper that provides some context. We'll focus on defining our custom functions.

This part of the code:

  def my_custom_method()
    ...
  end

defines a Ruby function. You can choose what to call the function, but the name should reflect what step is being done. The example above defined create_view and delete_view. Let's define a simple (but not very useful) function that tells us how many documents are in a database. We can replace my_custom_method(), so we'll start with this:

class ServerConfig
  def get_document_count()
    @logger.info(@properties["ml.content-db"])
  end
end

Note that Ruby, by convention, uses underscores to separate words.

Having declared the function, we need to add some XQuery code. The XQuery gets wrapped in %Q{ }. [TODO]

Specifying the Database Target

[TODO]

Decorating Existing Commands

In addition to adding new commands, you can make the existing commands do extra steps. Suppose you want the "deploy content" command to do something extra. Add this to deploy/app_specific.rb:

alias_method :original_deploy_content, :deploy_content
def deploy_content
  original_deploy_content

  execute_query(%Q{
    (: put your xquery here :)
  },
  :db_name => @properties["ml.app-name"])
end

This “overrides” deploy content. You can call it with:

./ml {env} deploy content

Examples

Adding/Deleting ODBC Views

class ServerConfig

  def delete_view()
    r = execute_query %Q{
      xquery version "1.0-ml"; 

      import module namespace view = "http://marklogic.com/xdmp/view" 
        at "/MarkLogic/views.xqy";

      try {
        view:remove(
          "main",
          "Compliance"
        )
      } catch ($e) { () }
      (: Deletes a view, of the 'main' schema that contains columns, with a scope on the element, 'html'. :)
    },
    { :db_name => @properties["ml.content-db"] }
  end

  def create_view()
    r = execute_query %Q{
      xquery version "1.0-ml"; 

      import module namespace view = "http://marklogic.com/xdmp/view" 
        at "/MarkLogic/views.xqy";

      try {
        view:schema-create(
          "main",
          ()
        )
      } catch ($e) {()},
      view:create(
          "main",
          "Compliance",
          view:element-view-scope(fn:QName("http://www.w3.org/1999/xhtml","html")),
          ( view:column("uri", cts:uri-reference()), 
            view:column("entityName", cts:path-reference('/xhtml:html/xhtml:head/xhtml:meta[@name eq "entityName"]/@content',("collation=http://marklogic.com/collation/"))),
            view:column("entityStreetAddress",  cts:path-reference('/xhtml:html/xhtml:head/xhtml:meta[@name eq "entityStreetAddress"]/@content',("collation=http://marklogic.com/collation/", ("nullable")))),
            view:column("entityCityAddress",  cts:path-reference('/xhtml:html/xhtml:head/xhtml:meta[@name eq "entityCityAddress"]/@content',("collation=http://marklogic.com/collation/", ("nullable")))),
            view:column("entityCountryAddress",  cts:path-reference('/xhtml:html/xhtml:head/xhtml:meta[@name eq "entityCountryAddress"]/@content',("collation=http://marklogic.com/collation//S2", ("nullable")))), 
            view:column("foreignEntityStatus",  cts:path-reference('/xhtml:html/xhtml:head/xhtml:meta[@name eq "foreignEntityStatus"]/@content',("collation=http://marklogic.com/collation/", ("nullable")))),
            view:column("intermediaryEntityStatus",  cts:path-reference('/xhtml:html/xhtml:head/xhtml:meta[@name eq "intermediaryEntityStatus"]/@content',("collation=http://marklogic.com/collation/codepoint", ("nullable")))),
            view:column("EIN",  cts:path-reference('/xhtml:html/xhtml:head/xhtml:meta[@name eq "EIN"]/@content',("collation=http://marklogic.com/collation/", ("nullable")))),
            view:column("docType", cts:path-reference('/xhtml:html/xhtml:head/xhtml:meta[@name eq "docType"]/@content',("collation=http://marklogic.com/collation//S1", ("nullable"))))
          ),
          () 
      )

      (: Creates a view, of the 'main' schema that contains columns, with a scope on the element, 'html'. :)
    },
    { :db_name => @properties["ml.content-db"] }
  end

end

Creating Alerting Triggers

def create_alerting_triggers()
  r = execute_query %Q{
    import module namespace alert = "http://marklogic.com/xdmp/alert" at "/MarkLogic/alert.xqy";
    import module namespace trgr = "http://marklogic.com/xdmp/triggers" at "/MarkLogic/triggers.xqy";

    if (alert:config-get("http://sample.com/audit/alert")) then ()
    else
      let $config :=
        alert:make-config(
          "http://sample.com/audit/alert",
          "Sample Change Alerting",
          "Alerting configuration for Sample",
          <alert:options/>)
      return
        alert:config-insert($config);


    import module namespace alert = "http://marklogic.com/xdmp/alert" at "/MarkLogic/alert.xqy";
    import module namespace trgr = "http://marklogic.com/xdmp/triggers" at "/MarkLogic/triggers.xqy";

    if (alert:config-get-trigger-ids(alert:config-get("http://sample.com/audit/alert"))) then ()
    else
      let $uri := "http://sample.com/audit/alert"
      let $trigger-ids :=
          alert:create-triggers(
            $uri,
            trgr:trigger-data-event(
              trgr:directory-scope("/ItemInfo/", "1"),
              trgr:document-content(("modify")),
              trgr:pre-commit()))
      let $config := alert:config-set-trigger-ids(alert:config-get($uri), $trigger-ids)
      return
        alert:config-insert($config);

    import module namespace alert = "http://marklogic.com/xdmp/alert" at "/MarkLogic/alert.xqy";
    import module namespace trgr = "http://marklogic.com/xdmp/triggers" at "/MarkLogic/triggers.xqy";

    if (alert:get-actions("http://sample.com/audit/alert", ("*"))) then ()
    else
      let $uri := "http://sample.com/audit/alert"
      let $action-add-mailfile :=
        alert:make-action(
          "add-to-inbox",
          "Add Audit Event to the User Inbox",
          xdmp:modules-database(),
          xdmp:modules-root(),
          "/lib/add-to-inbox.xqy",
          <alert:options/>
        )
      return
        alert:action-insert($uri, $action-add-mailfile)
  },
  { :app_name => @properties["ml.app-name"] }
end

Creating Plain Triggers

In this example, the "deploy modules" command gets overridden so that in addition to its default behavior, the command will also deploy some triggers. If you do this, you'd probably want to similarly override "clean modules".

class ServerConfig
  alias_method :original_deploy_modules, :deploy_modules
  def deploy_modules()
    original_deploy_modules

    r = execute_query(%Q{
        xquery version "1.0-ml";

        import module namespace trgr="http://marklogic.com/xdmp/triggers" 
           at "/MarkLogic/triggers.xqy";

        xdmp:log("Installing triggers.."),

        try {
          trgr:remove-trigger("CreateTrigger"),
          trgr:remove-trigger("ModifyTrigger")
        } catch ($ignore) {
        };


        xquery version "1.0-ml";

        import module namespace trgr="http://marklogic.com/xdmp/triggers" 
           at "/MarkLogic/triggers.xqy";

        trgr:create-trigger("CreateTrigger", "Trigger to enrich XML ingested via MLCP", 
          trgr:trigger-data-event(
            trgr:directory-scope("/ingest/", "1"),
            trgr:document-content("create"),
            trgr:pre-commit()
          ),
          trgr:trigger-module(xdmp:modules-database(), "/", "/triggers/enrich-xml.xqy"),
          fn:true(),
          xdmp:default-permissions(),
          fn:false()
        ),

        trgr:create-trigger("ModifyTrigger", "Trigger to enrich XML ingested via MLCP", 
          trgr:trigger-data-event(
            trgr:directory-scope("/ingest/", "1"),
            trgr:document-content("modify"),
            trgr:pre-commit()
          ),
          trgr:trigger-module(xdmp:modules-database(), "/", "/triggers/enrich-xml.xqy"),
          fn:true(),
          xdmp:default-permissions(),
          fn:false()
        )
      },
      { :app_name => @properties["ml.app-name"] }
    )
  end
end

Note that as written, the triggers are deployed to the modules database. To deploy it to your Triggers database, you can add this at the end, near :app-name --

{ 
  :app_name => @properties["ml.app-name"],
  :db_name => @properties["ml.triggers-db"]
}
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.