Skip to main content

Creating a DXA Java Module (part 2)

In my previous post Creating a DXA Java Module, I started presenting the steps for creating the Tridion items on the Content Manager needed for the new DXA module. In this current post, I present the Java code and configuration for having the DXA module run in a web-application.

I wrote my code as a separate JAR using IntelliJ and then running the DXA v1.5 web-application on Tomcat as part of the standard DXA reference implementation project dxa-web-application-java (available in GIT at https://github.com/sdl/dxa-web-application-java/tree/release/1.5).

What follows are the steps on how to create the DXA Java module classes and configurations.

1. Create the IntelliJ Emerald Module

In IntelliJ, create a new Java Module and enable Maven for it. Add the following dependencies in its pom.xml file:

    <dependencies>
        <dependency>
            <groupId>com.sdl.dxa</groupId>
            <artifactId>dxa-common-api</artifactId>
        </dependency>

        <!-- Servlet API -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>

        <!-- Spring Framework -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
        </dependency>
    </dependencies>

The final layout of the DXA module looks like the following:


2. Module Initializer Class

Configuration class EmeraldInitializer defines the mapping between ViewModels and class or domain models.

It extends AbstractInitializer, so it defines the getAreaName method, which returns the name of this module (i.e. Emerald).

Last, but not least it triggers the registration of the module's entities with the SemanticRegistryMapper, by defining method registerModels as a Spring post construct initialization method. Without this feature, only the entity models defined in the view-domain mappings are going to be registered with the Semantic Mapper. This has as undesired effect, the loss of embedded entities being added to the Semantic Mapper registries. As such, there will be no values for the embedded fields once the model is built.

package com.mihaiconsulting.dxa.emerald;

import com.mihaiconsulting.dxa.emerald.model.entity.MainNavigation;
import com.sdl.webapp.common.api.mapping.semantic.SemanticMappingRegistry;
import com.sdl.webapp.common.api.mapping.views.AbstractInitializer;
import com.sdl.webapp.common.api.mapping.views.ModuleInfo;
import com.sdl.webapp.common.api.mapping.views.RegisteredViewModel;
import com.sdl.webapp.common.api.mapping.views.RegisteredViewModels;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@org.springframework.context.annotation.Configuration
public class EmeraldInitializer {

    @RegisteredViewModels({
            @RegisteredViewModel(viewName = "MainNavigation", modelClass = MainNavigation.class)
    })
    @Component
    @ModuleInfo(name = "Emerald module", areaName = "Emerald")
    public static class EmeraldViewInitializer extends AbstractInitializer {

        @Autowired
        private SemanticMappingRegistry semanticMappingRegistry;

        @Override
        protected String getAreaName() {
            return "Emerald";
        }

        @PostConstruct
        private void registerModels() {
            semanticMappingRegistry.registerEntities(getClass().getPackage().getName());
        }
    }
}

Notice the mapping between view MainNavigation and model class MainNavigation.java. I will present those classes below.

3. The Models

Emerald module defines only two simple model classes MainNavigation and NavigationLinks. These are simple Java beans, which need to contain annotations that help the Semantic Mapper fill their properties with values from generic DD4T models.

The MainNavigation class is annotated with entityName MainNavigation which is in fact the Schema Root Element Name that maps to this model class. It also defines the vocabulary to use while mapping; this is the Schema target namespace defined in Tridion Content Manager, and the standard vocabulary prefix tri.

The MainNavigation.class is also used in the mapping in EmeraldInitializer between the view model and the class model that serves it.

Next, the class extends AbstractEntityModel class which helps identifying it as a model entity and provides additional metadata for XPM and other ids.

Finally, each property is annotated with mapping information from the Schema field using the vocabulary prefix and field XML name.

package com.mihaiconsulting.dxa.emerald.model.entity;

import com.sdl.webapp.common.api.mapping.semantic.annotations.SemanticEntity;
import com.sdl.webapp.common.api.mapping.semantic.annotations.SemanticProperty;
import com.sdl.webapp.common.api.model.entity.AbstractEntityModel;

import java.util.List;

@SemanticEntity(entityName = "MainNavigation", vocabulary = "http://www.sdl.com/web/schemas/core", prefix = "tri")
public class MainNavigation extends AbstractEntityModel {

    @SemanticProperty("tri:name")
    private String name;

    @SemanticProperty("tri:links")
    private List<NavigationLinks> links;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<NavigationLinks> getLinks() {
        return links;
    }

    public void setLinks(List<NavigationLinks> links) {
        this.links = links;
    }
}

The embedded entity NavigationLinks is presented below. Notice that it is an entity on its own that also extends AbstractEntityModel.

package com.mihaiconsulting.dxa.emerald.model.entity;

import com.sdl.webapp.common.api.mapping.semantic.annotations.SemanticEntity;
import com.sdl.webapp.common.api.mapping.semantic.annotations.SemanticProperty;
import com.sdl.webapp.common.api.model.entity.AbstractEntityModel;

@SemanticEntity(entityName = "NavigationLinks", vocabulary = "http://www.sdl.com/web/schemas/core", prefix = "tri")
public class NavigationLinks extends AbstractEntityModel {

    @SemanticProperty("tri:url")
    private String url;

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }
}

4. JSP View Model

The ViewModel is called MainNavigation as per the mapping present in EmeralInitializer class when registering the view with the domain model. The view name is also referenced from the Component Template metadata defined in Tridion. The Metadata Schema field view defines the DXA module name and JSP view name in the format ModuleName:ViewName.

DXA uses a naming convention referring to the location of JSP views. They must be under path WEB-INF / Views / DXA-Module-Name / Entity. This makes the path of our view /WEB-INF/Views/Emerald/Entity.

Also, since this IntelliJ Java module is packaged as a JAR, the JSP views must be available in the JAR's classpath as resources. As such, we must put the JSP in the JAR under path /META-INF/WEB-INF/Views/Emerald/Entity/MainNavigation.jsp.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<jsp:useBean id="entity" type="com.mihaiconsulting.dxa.emerald.model.entity.MainNavigation" scope="request"/>

<div>
    <p>Name: ${entity.name}</p>

    <c:if test="${not empty entity.links}">
        Links:
        <ul>
            <c:forEach var="link" items="${entity.links}">
                <li>${link.url}</li>
            </c:forEach>
        </ul>
    </c:if>
</div>

The JSP/JSTL code in the view is trivial. It simply outputs the fields of the MainNavigation model and its multi-valued links. Notice how the entity bean is read from the request.

5. Spring Configuration

One additional configuration is required that indicates Spring framework should process the new DXA module for Spring annotations. We must tell Spring to scan for components under a certain package. In this case, we tell Spring to scan package com.mihaiconsulting.dxa.emerald and process the beans, controllers, auto-wired properties, post construct initialization methods.

DXA imports all /META-INF/spring-context.xml files it finds in all classpaths. As such, I defined one such file and placed it in the JAR's /META-INF/ folder.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.mihaiconsulting.dxa.emerald" />

</beans>

6. Run the Web-Application

Having all these configurations and classes, we can now run the web-application. Everything should be in place now to retrieve and build the models, then to render them in the JSP view:




Comments

Popular posts from this blog

Scaling Policies

This post is part of a bigger topic Autoscaling Publishers in AWS . In a previous post we talked about the Auto Scaling Groups , but we didn't go into details on the Scaling Policies. This is the purpose of this blog post. As defined earlier, the Scaling Policies define the rules according to which the group size is increased or decreased. These rules are based on instance metrics (e.g. CPU), CloudWatch custom metrics, or even CloudWatch alarms and their states and values. We defined a Scaling Policy with Steps, called 'increase_group_size', which is triggered first by the CloudWatch Alarm 'Publish_Alarm' defined earlier. Also depending on the size of the monitored CloudWatch custom metric 'Waiting for Publish', the Scaling Policy with Steps can add a difference number of instances to the group. The scaling policy sets the number of instances in group to 1 if there are between 1000 and 2000 items Waiting for Publish in the queue. It also sets the

Toolkit - Dynamic Content Queries

This post if part of a series about the  File System Toolkit  - a custom content delivery API for SDL Tridion. This post presents the Dynamic Content Query capability. The requirements for the Toolkit API are that it should be able to provide CustomMeta queries, pagination, and sorting -- all on the file system, without the use third party tools (database, search engines, indexers, etc). Therefore I had to implement a simple database engine and indexer -- which is described in more detail in post Writing My Own Database Engine . The querying logic does not make use of cache. This means the query logic is executed every time. When models are requested, the models are however retrieved using the ModelFactory and those are cached. Query Class This is the main class for dynamic content queries. It is the entry point into the execution logic of a query. The class takes as parameter a Criterion (presented below) which triggers the execution of query in all sub-criteria of a Criterio

Running sp_updatestats on AWS RDS database

Part of the maintenance tasks that I perform on a MSSQL Content Manager database is to run stored procedure sp_updatestats . exec sp_updatestats However, that is not supported on an AWS RDS instance. The error message below indicates that only the sa  account can perform this: Msg 15247 , Level 16 , State 1 , Procedure sp_updatestats, Line 15 [Batch Start Line 0 ] User does not have permission to perform this action. Instead there are several posts that suggest using UPDATE STATISTICS instead: https://dba.stackexchange.com/questions/145982/sp-updatestats-vs-update-statistics I stumbled upon the following post from 2008 (!!!), https://social.msdn.microsoft.com/Forums/sqlserver/en-US/186e3db0-fe37-4c31-b017-8e7c24d19697/spupdatestats-fails-to-run-with-permission-error-under-dbopriveleged-user , which describes a way to wrap the call to sp_updatestats and execute it under a different user: create procedure dbo.sp_updstats with execute as 'dbo' as