Skip to main content

Label Service in DD4T Java

Many times over we need to implement a Label service mechanism in our projects. This post shows one such approach for DD4T Java.

The idea is to have a Labels Page published from Tridion containing the Labels Component on it. The labels are simply multi-valued Key - Value pairs represented by an Embedded Schema.

The Labels page is read in DD4T and the labels on it are parsed and loaded into a java.util.Map. The front end code employs a custom JSP tag to read the label map and display the associated value for a given key.

Content Manager Part

We use Schema Label with a field 'label' of type Embedded Schema, multi-valued.

The Embedded Schema is 'Embedded label' and contains two field, both single value text fields: 'key' and 'value'.

We create a Component 'Labels' and populate it with our labels. This Component can be localized and translated in sub-Publications, as the Label Service can use context-aware Components to load the set of labels in the right locale.


Further, we place the Labels Component on a Page and publish it with DD4T Component and Page Templates.

DD4T Content Delivery Part

Everything starts with an interface - LabelService, which we define in the dd4t-api project, in package org.dd4t.core.resolvers:

public abstract interface LabelService {
    public Map<String, String> load(int publicationId) throws IOException;
    public String getLabel(String key) throws IOException;
    public String getLabel(String key, int publicationId) throws IOException;
}

The method load is responsible for loading the label Map. The other two getLabel method retrieve a label for a given key potentially in a given Publication context.

An implementation of the interface is in class LabelServiceBase, part of project dd4t-core, in package org.dd4t.core.resolvers.impl:

public abstract class LabelServiceBase implements LabelService {
    @Autowired
    protected PublicationResolver publicationProvider;
    @Autowired
    protected CacheProvider cacheProvider;

    @Override
    public String getLabel(String key) throws IOException {
        return getLabel(key, publicationProvider.getPublicationId());
    }

    @Override
    public String getLabel(String key, int publicationId) throws IOException {
        String mapKey = getMapKey(publicationId);
        Map<String, String> labelMap = (Map<String, String>) cacheProvider.loadFromLocalCache(mapKey);
        if (labelMap == null) {
            synchronized (mapKey) {
                labelMap = (Map<String, String>) cacheProvider.loadFromLocalCache(mapKey);
                if (labelMap == null) {
                    labelMap = load(publicationId);
                    if (labelMap == null) {
                        return key;
                    }
                }
            }
        }
        String result = labelMap.get(key);
        return result == null ? key : result;
    }

    protected String getMapKey(int publicationId) {
        return "LabelMap-" + publicationId;
    }
}

The base implementation makes use of a PublicationResolver that provides the current Publication context, when a Publication is not specified. It also uses a CacheProvider (from package org.dd4t.providers) that provides a generic way to store the label maps. These objects are provided using Spring's Dependency Injection mechanism.

The main method in the class - getLabel tries to retrieve a particular label, and if not found in cache, it attempts to (re-)load the labels for the given context Publication.

The implementation of the load method is left to be specified by the actual DD4T web application layer. Such an implementation makes use of a properties file to read the URL of the Labels page. Then the PageFactory reads the page model and we extract the Label key--value pairs from it.

public class MyLabelService extends LabelServiceBase {

    private static final MyLabelService _instance = new MyLabelService();
    private final String labelPageURL;

    private MyLabelService() throws IOException {
        InputStream input = LabelServiceBase.class.getClassLoader().getResourceAsStream("myapp.properties");
        Properties properties = new Properties();
        properties.load(input);

        labelPageURL = properties.getProperty("labelpage.url");
    }

    public static MyLabelService getInstance() {
        return _instance;
    }

    @Override
    public synchronized Map<String, String> load(int publicationId) throws IOException {
        Map<String, String> result = new TreeMap<>();

        try {
            PageFactory pageFactory = PageFactory.getInstance();
            GenericPage labelPage = (GenericPage) pageFactory.findPageByUrl(labelPageURL, publicationProvider.getPublicationId());

            List<ComponentPresentation> componentPresentations = labelPage.getComponentPresentations();
            ComponentPresentation componentPresentation = componentPresentations.get(0);
            GenericComponent labelComponent = componentPresentation.getComponent();

            Label label = (Label) ModelFactory.createInstance(labelComponent);
            String mapKey = getMapKey(publicationId);

            for (EmbeddedLabel embeddedLabel : label.getLabelList()) {
                String key = embeddedLabel.getKey();
                String value = embeddedLabel.getValue();
                result.put(key, value);
            }

            int itemId = new TCMURI(labelPage.getId()).getItemId();
            cacheProvider.storeInItemCache(mapKey, result, publicationId, itemId);
        } catch (ItemNotFoundException | FilterException | ModelException | ParseException | SerializationException e) {
            throw new IOException("Failed to load labels", e);
        }

        return result;
    }
}

The load method uses specialized model view classes Label and EmbeddedLabel:

@TridionModel(rootElementName = "label")
public class Label extends BaseModel {

    @TridionField(fieldName = "label", fieldType = TridionFieldType.Embedded)
    private List<EmbeddedLabel> labelList;

    public List<EmbeddedLabel> getLabelList() {
        return labelList;
    }

    public void setLabelList(List<EmbeddedLabel> labelList) {
        this.labelList = labelList;
    }
}

@TridionModel(rootElementName = "embeddedLabel")
public class EmbeddedLabel extends BaseModel {

    @TridionField
    private String key;
    @TridionField
    private String value;

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

Finally, the whole mechanism can be used from a JSP Tag or function like this:

MyLabelService labelService = MyLabelService.getInstance();
String seeDetailsLabel = labelService.getLabel("seedetails");


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...

I Have Gone Dark

Maybe it's the Holidays, but my mood has gone pretty dark. That is, regarding the look and feel of my computer and Tridion CME, of course. What I did was to dim the lights on the operating system, so I installed Placebo themes for Windows 7 . I went for the Ashtray look -- great name :) My VM looks now like this: But, once you change the theme on Windows, you should 'match' the theme of your applications. Some skin easily, some not. The Office suite has an in-built scheme, which can be set to Black , but it doesn't actually dim the ribbon tool bars -- it looks quite weird. Yahoo Messenger is skinnable, but you can't change the big white panels where you actually 'chat'. Skype is not skinnable at all. For Chrome, there are plenty of grey themes. Now i'm using Pro Grey . But then I got into changing the theme of websites. While very few offer skinnable interfaces (as GMail does), I had to find a way to darken the websites... Enter Stylish -- a pl...

REL Standard Tag Library

The RSTL is a library of REL tags providing standard functionality such as iterating collections, conditionals, imports, assignments, XML XSLT transformations, formatting dates, etc. RSTL distributable is available on my Google Code page under  REL Standard Tag Library . Always use the latest JAR . This post describes each RSTL tag in the library explaining its functionality, attributes and providing examples. For understanding the way expressions are evaluated, please read my post about the  Expression Language used by REL Standard Tag Library . <c:choose> / <c:when> / <c:otherwise> Syntax:     <c:choose>         <c:when test="expr1">             Do something         </c:when>         <c:when test="expr2">             Do something else         </c:when...