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

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

Content Delivery Monitoring in AWS with CloudWatch

This post describes a way of monitoring a Tridion 9 combined Deployer by sending the health checks into a custom metric in CloudWatch in AWS. The same approach can also be used for other Content Delivery services. Once the metric is available in CloudWatch, we can create alarms in case the service errors out or becomes unresponsive. The overall architecture is as follows: Content Delivery service sends heartbeat (or exposes HTTP endpoint) for monitoring Monitoring Agent checks heartbeat (or HTTP health check) regularly and stores health state AWS lambda function: runs regularly reads the health state from Monitoring Agent pushes custom metrics into CloudWatch I am running the Deployer ( installation docs ) and Monitoring Agent ( installation docs ) on a t2.medium EC2 instance running CentOS on which I also installed the Systems Manager Agent (SSM Agent) ( installation docs ). In my case I have a combined Deployer that I want to monitor. This consists of an Endpoint and a

Debugging a Tridion 2011 Event System

OK, so you wrote your Tridion Event System. Now it's time to debug it. I know this is a hypothetical situtation -- your code never needs any kind of debugging ;) but indulge me... Recently, Alvin Reyes ( @nivlong ) blogged about being difficult to know how exactly to debug a Tridion Event System. More exactly, the question was " What process do I attach to for debugging even system code? ". Unfortunately, there is no simple or generic answer for it. Different events are fired by different Tridion CM modules. These modules run as different programs (or services) or run inside other programs (e.g. IIS). This means that you will need to monitor (or debug) different processes, based on which events your code handles. So the usual suspects are: dllhost.exe (or dllhost3g.exe ) - running as the MTSUser is the SDL Tridion Content Manager COM+ application and it fires events on generic TOM objects (e.g. events based on Tridion.ContentManager.Extensibility.Events.CrudEven