This version of the manual refers to an earlier version of the software.

Delegate Script

The delegate script mechanism enables the use of custom Ruby methods as "hooks" to provide dynamic information back to the image server. A truly customized image server can be created with minimal code.

Delegate methods are invoked by a JRuby interpreter bundled into the image server. There is no need to install an external Ruby environment and no need to know Java or any of the image server's internal API.

Prior to version 3.2, the JRuby interpreter was compatible with Ruby 2.2. Since version 3.2, it has been compatible with version 2.3.

The delegate script mechanism is disabled by default. To enable it, follow these steps:

  1. Copy the sample delegate script, delegates-3.4.rb.sample, included in the distribution, to delegates.rb.
  2. Reference it from the delegate_script.pathname configuration option.
  3. Set delegate_script.enabled to true.

Rules

While the arguments and return types of each method will vary, all delegate methods must be contained within a Cantaloupe module. Inside a method, anything goes, and you can use any (non-platform-native) gems that you have installed with gem install.

Starting in version 3.3, the delegate script is reloaded whenever the script file changes. (Previously, it was reloaded on each request.) Be aware, though, that code that has already been loaded into the JRuby runtime cannot be unloaded. For example, when a method is changed, the new version will overwrite the old version; but constants cannot be redefined.

Because delegate methods will be called frequently, they should be written with efficiency in mind.

Note: generally, neither method arguments nor return values are sanitized or validated. Be very careful to write defensive, injection-safe code.

Gems

JRuby can load most Ruby gems, except those that have been built with native extensions. To import a gem, use the require statement:

require 'mygem'

require searches for gems based on the $GEM_PATH environment variable, falling back to $GEM_HOME if that is not defined. If JRuby fails to find your gem, check your $GEM_PATH. If you installed the gem using gem install, check the output of gem env (particularly the "gem paths" section) to see where it might have been installed, and ensure that those locations are present in $GEM_PATH.

Caching

Since version 3.3, the delegate_script.cache.enabled option is available to cache the results of delegate method invocations. The cache is an in-memory least-recently-used (LRU) cache with infinite time-to-live and a maximum size auto-computed based on the maximum JVM heap size. When the limit is approached, the oldest invocations will be purged automatically.

The cache is not persisted. It will be lost when the application is stopped.

Note that cached invocations are not purged when the script file is edited and auto-reloaded.

Since version 3.4, the invocation cache can be purged manually using the HTTP API.


Logging

Since version 3.4, delegate methods may access a logger that writes to the application log:

require 'java'

logger = Java::edu.illinois.library.cantaloupe.script.Logger
logger.trace('Hello world')
logger.debug('Hello world')
logger.info('Hello world')
logger.warn('Hello world')
logger.error('Hello world')

Example

Here is an example of a script used by FilesystemResolver that performs a Solr query to return a pathname based on an identifier. The documentation in that section describes the contract that this method must abide by: its name, arguments, and return value.

require 'cgi'
require 'net/http'

module Cantaloupe

  module FilesystemResolver
    ##
    # Used by FilesystemResolver's ScriptLookupStrategy.
    #
    # @param identifier [String] Image identifier
    # @param context [Hash] Context for this request
    # @return [String,nil] Absolute pathname of the image corresponding to the
    #                      given identifier, or nil if not found.
    #
    def self.get_pathname(identifier, context)
      uri = 'http://localhost:8983/solr/collection1/select?q=' +
          CGI.escape('id:"' + identifier + '"') +
          '&fl=pathname_si&wt=ruby'
      uri = URI.parse(uri)

      http = Net::HTTP.new(uri.host, uri.port)
      request = Net::HTTP::Get.new(uri.request_uri)
      response = http.request(request)
      return nil if response.code.to_i >= 400

      results = eval(response.body)['response']['docs']
      results.any? ? results.first['pathname_si'] : nil
    end
  end

end

Testing Script Methods

Using the example above, get_pathname() could be tested by adding the following line to the end of the script:

puts Cantaloupe::FilesystemResolver::get_pathname('identifier-to-test', {})
And running it on the command line with a command like: ruby delegates.rb. The method output will appear in the console.

Note that the ruby command will normally invoke the standard ("MRI") Ruby interpreter, and not the JRuby interpreter. While they mostly work the same, one thing to be aware of is that gems with platform-native extensions will not work in JRuby. For that reason, you might want to install a standalone JRuby interpreter and test with that instead. (Something like RVM can make it easier to switch between different versions of the Ruby interpreter.)