Time for action – writing a new ResponseWriter plugin with the Thymeleaf library

There are many options for new customizations, depending on your use cases, and the continuous evolution of the framework is still simplifying things and making room for new improvements. For example, I feel that it is interesting to adapt the very flexible Thymeleaf template system to play with Solr as a ResponseWriter.

Note

Thymeleaf is a Java XML/XHTML/HTML5 template engine. It can serve XHTML/HTML5 in web applications or process any XML file: http://www.thymeleaf.org/.

  1. A basic outline of the source should be similar to the following code:
    class ThymeleafResponseWriter extends QueryResponseWriter {
      
      override def getContentType(request: SolrQueryRequest, response: SolrQueryResponse) = "text/html"
      override def init(namedList: NamedList[_]) = logger.debug("THYMELEAF PARAMETERS: {}", namedList.toString())
      
      override def write(writer: Writer, request: SolrQueryRequest, response: SolrQueryResponse) = {
        
        val sb: StringBuffer = new StringBuffer
        
        val templateDir = new File(request.getCore().getDataDir() + "/../conf/thymeleaf").getCanonicalPath()
        val template = new File(templateDir, "home.html").getAbsolutePath()
        
        val now = new SimpleDateFormat("dd MMMM YYYY - HH:mm").format(new Date())
        val resultContext = response.getValues().get("response").asInstanceOf[ResultContext]
        val doclist: SolrDocumentList = SolrPluginUtils.docListToSolrDocumentList(
          resultContext.docs, request.getSearcher(),
          response.getReturnFields().getLuceneFieldNames(), null
        )
        // inject new objects into the Thymeleaf context, to make them available in the template
         val ctx = new Context()
         ctx.setVariable("today", now)
         ctx.setVariable("response", response)
         ctx.setVariable("resultContext", resultContext)
         ctx.setVariable("request", request)
         ctx.setVariable("core", request.getCore())
         ctx.setVariable("docs", doclist)
              val templateEngine = new TemplateEngine()
         val resolver = new FileTemplateResolver()
         resolver.setCharacterEncoding("UTF-8")
         templateEngine.setTemplateResolver(resolver)
         templateEngine.initialize()
              val result = templateEngine.process(template, ctx)
         
        sb.append(new String(result.getBytes())
        writer.write(sb.toString())
      }
      
    }
  2. The choice of Thymeleaf is driven by the simplicity of its template system. We can write a template in plain real HTML5, and we can modify the template without restarting Solr once the writer is up. The aim is to play with a simple example that can live on its own and can be improved in the direction of a real embedded near-template system (such as the existing VelocityResponseWriter), but introducing some more fun and simplicity.
  3. To give you an idea of the results, look at the following simple HTML snippet, which also includes a little JavaScript code:
    <h1 th:text="${'Solr Core: '+core.getCoreDescriptor().getName()}">CORE NAME</h1>
    <section id="documents" th:each="doc : ${docs}">
      <article class="document">
        <header><h2 th:text="*{doc.get('title')}">A TITLE for the DOC</h2></header>
        <div class="content" th:text="*{doc.get('abstract')}">LOREM IPSUM..</div>
      </article>
    </section>
    
    <script type="text/javascript" th:inline="text">
    /*<![CDATA[*/
    // you can post-process view data here :-)
    $(function(){
      var c = $(core_name)
      c.html(c.html().replace('_', ' '))
    });
    /*]]>*/
    </script>

    Note

    The example still contains too much code in the template counterpart. But I tried to find a good balance between simplicity and readability; so, please feel free if you want to improve it, or even completely rewrite it in a more appropriate way.

  4. You will find the example in the /SolrStarterBook/solr-app/chp09/arts_thymeleaf directory, including libraries and more detailed configurations. For example, adding the new response writer is as simple as adding in solrconfig.xml as follows:
    <queryResponseWriter name="thymeleaf"
      class="it.seralf.solrbook.writers.thymeleaf.ThymeleafResponseWriter">
      <str name="content-type">text/html; charset=UTF-8</str>
      <str name="template">home.html</str>
    </queryResponseWriter>
  5. Once the configuration is ready, our new core can be started in the usual way. If we put the URL http://localhost:8983/solr/arts_thymeleaf/select?q=artist:*&wt=thymeleaf&indent=true in the browser, we will obtain an output similar to the one in the following screenshot:
Time for action – writing a new ResponseWriter plugin with the Thymeleaf library

I know that this is not exactly pretty, and it lacks so much information that it seems broken. The point is that this is not broken, and it is instead the product of our very first simple exploration in writing a working (let's say it's almost working) custom component. You can use it as a playground for experimentations, and easily extend it by working on the HTML template directly to add more features. All you have to do is read a little about writing attributes with Thymeleaf.

What just happened?

The class inherits the contract of a QueryResponseWriter class, so we have to override the getContentType and init methods. The first is trivial; the second is not really used here, but it's interesting to track the parameters received by a NamedList instance.

The entire logic lies in the write method. The signature is similar to a standard service method for a Servlet. So, we have a request and a response wrapper object—in this case, a SolrQueryRequest and a SolrQueryResponse in particular.

From the request, we can obtain a reference to the currently used parameters and the core itself. This is useful to find resources (for example, the HTML template files) by using a relative path. But, the same approach can be used with every configuration and internal objects that is handled by a core reference.

From the response, we can obtain a reference to the results of our query, such as the list of documents and number of facets. We can handle all the things we generally see with an XML or JSON serialization, the main difference will be in the names and in the hierarchy. So, we will have to study a little, and we cannot expect to find an exact match with the XML serialization. For example, the best way is to open the Java API for Solr and explore what we can do by using them, for example for a result context at: http://lucene.apache.org/solr/4_5_0/solr-core/index.html?org/apache/solr/response/ResultContext.html. Note that if there exists an object of the ResultContext type that encapsulates the query and document list objects, we also need to create a Thymeleaf context. This will be the object where we will inject all the data that we will be able to manage from the HTML counterpart.

Tip

If you look at the HTML template, Thymeleaf permits us to write valid HTML5 templates and construct the page as we want—even using Lorem-Ipsum and similar placeholder text when needed. At runtime, the processor will take the directive we placed on the tags by using the Thymeleaf dialect (http://www.thymeleaf.org/doc/html/Using-Thymeleaf.html) to substitute the placeholder with the actual data. What I like in this approach is that once the skeleton is created for our plugin, the HTML views are editable without recompilation; they are parsed at runtime on the server.

If we want to play with this almost hello world example, we can simply request by using the following command:

>> curl -X 'http://localhost:8983/solr/arts_thymeleaf/select?q=*:*&wt=thymeleaf'

In this way, we are directly exposing the Solr API and providing a new simple HTML5 rendering of the data. This is not exactly what we want to do on a production environment; but this can be useful for prototyping, for fast presentations of basic features, and even for creating specific views for monitoring or exploration of data on the internal network.

Moreover, we are writing the code in a very similar way then and if we are handling HTML with JavaScript at the client side, with improvements on performance (all the logic is at the server and the client will receive only the HTML part), and the assumption to have a well formed, validated HTML code. I feel that this approach is easy to understand for a beginner, although it contains a robust way to proceed, which can be used later in more advanced contexts, without too much boilerplate code.

The data injection is simple, because HTML attributes are used to define rules and placeholders for the data. It will then be syntactically compliant to HTML5. For example, to inject all the titles using some <section> elements, all we have to do is iterate over the collection of documents <section id="documents" th:each="doc : ${docs}"> and read the specific data for every document with something similar to: <h2 th:text="*{doc.get('title')}">some-dummy-text</h2>. At runtime, the text will be overwritten by the actual one; and we can associate the Thymeleaf rules to the elements we want, in order to obtain a natural mapping. In this way, your team can be easily involved in prototyping from the earlier stages, because the designer can work on valid HTML templates (using their own editor) that are loosely coupled to the software internals as well.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.60.62