org.dd4t.mvc.utils.RenderUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.dd4t.mvc.utils.RenderUtils.java

Source

/*
 * Copyright (c) 2015 SDL, Radagio & R. Oudshoorn
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.dd4t.mvc.utils;

import org.apache.commons.lang3.StringUtils;
import org.dd4t.contentmodel.Component;
import org.dd4t.contentmodel.ComponentPresentation;
import org.dd4t.contentmodel.ComponentTemplate;
import org.dd4t.contentmodel.Field;
import org.dd4t.contentmodel.impl.TextField;
import org.dd4t.core.databind.BaseViewModel;
import org.dd4t.core.exceptions.FactoryException;
import org.dd4t.core.exceptions.RenderException;
import org.dd4t.core.factories.impl.ComponentPresentationFactoryImpl;
import org.dd4t.core.util.Constants;
import org.dd4t.core.util.TCMURI;
import org.dd4t.databind.util.DataBindConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *
 */
public class RenderUtils {

    private static final Logger LOG = LoggerFactory.getLogger(RenderUtils.class);

    private RenderUtils() {

    }

    /**
     * If the component template has a template parameter viewName, the value of
     * that parameter is returned. Otherwise the title of the component template
     * is returned.
     */
    public static String getViewName(ComponentPresentation cp) {
        ComponentTemplate componentTemplate = cp.getComponentTemplate();
        Map<String, Field> metadata = componentTemplate.getMetadata();

        if (metadata != null && metadata.containsKey(DataBindConstants.VIEW_MODEL_DEFAULT_META_KEY)) {
            String viewName = (String) metadata.get(DataBindConstants.VIEW_MODEL_DEFAULT_META_KEY).getValues()
                    .get(0);
            if (StringUtils.isNotEmpty(viewName)) {
                return viewName.toLowerCase();
            }
        }

        return componentTemplate.getTitle().toLowerCase();
    }

    /**
     * Filters the list of component presentations based on the processors provided
     * by schemaName and templateName. To align with the .Net variant of DD4T
     * the schema and template matches uses the startsWith method and there also
     * is support for the negation of the expression.
     */
    public static List<ComponentPresentation> filterComponentPresentations(
            final List<ComponentPresentation> componentPresentations, final String schemaName,
            final String rootElementName, final String viewName, final String region) {
        boolean regionIsSet = false;

        if (region != null) {
            regionIsSet = true;
            LOG.debug("Fetching component presentations for region: {}", region);
        } else {
            LOG.debug("Component Presentations By Region is called, but no region is set.");
            LOG.debug("Removing component presentations which do have a region set.");
        }

        final List<ComponentPresentation> componentPresentationsToRender = new ArrayList<>();

        if (StringUtils.isNotEmpty(schemaName) || StringUtils.isNotEmpty(rootElementName)
                || StringUtils.isNotEmpty(viewName)) {

            for (ComponentPresentation cp : componentPresentations) {
                String componentSchema = cp.getComponent().getSchema().getTitle();
                String componentRootElement = cp.getComponent().getSchema().getRootElement();
                String view = getViewName(cp);

                if (match(componentSchema.toLowerCase(), schemaName) && match(componentRootElement, rootElementName)
                        && match(view, viewName) && isComponentPresentationInRegion(region, regionIsSet, cp)) {
                    componentPresentationsToRender.add(cp);
                }
            }

        } else {
            for (ComponentPresentation cp : componentPresentations) {
                if (isComponentPresentationInRegion(region, regionIsSet, cp)) {
                    componentPresentationsToRender.add(cp);
                }
            }
        }
        return componentPresentationsToRender;
    }

    private static boolean isComponentPresentationInRegion(final String region, final boolean regionIsSet,
            final ComponentPresentation cp) {
        final Map<String, Field> metadata = cp.getComponentTemplate().getMetadata();
        final Field currentRegion = metadata != null ? metadata.get("region") : null;
        if (currentRegion != null && currentRegion.getValues() != null && !currentRegion.getValues().isEmpty()) {
            if (regionIsSet) {
                boolean removeCp = true;
                for (Object regionValue : currentRegion.getValues()) {
                    if (regionValue.toString().equalsIgnoreCase(region)) {
                        LOG.debug("CP Region matches configured region: {}", regionValue.toString());
                        removeCp = false;
                    }
                }
                if (removeCp) {
                    LOG.debug("No matching region found. Not Rendering.");
                    return false;
                } else {
                    return true;
                }
            } else {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("This is a CP with a Region, but there is no region configured on the Tag.");
                    LOG.debug("Not rendering CP: {}-{}", cp.getComponent().getId(),
                            cp.getComponentTemplate().getId());
                    for (Object regionValue : currentRegion.getValues()) {
                        LOG.debug("With Region: {}", regionValue.toString());
                    }
                }
                return false;
            }
        } else {
            LOG.debug("No Region set on CP: {} - {}", cp.getComponent().getId(), cp.getComponentTemplate().getId());
            if (regionIsSet) {
                LOG.debug("Not rendering this CP as a region is set on the Tag.");
                return false;
            } else {
                LOG.debug("Rendering as no region is set on either the Tag or the CP");
                return true;
            }
        }
    }

    private static boolean match(final String value, final String expressionParam) {
        if (StringUtils.isEmpty(expressionParam)) {
            return true;
        }

        // Check for negation of the expressions, note that we trim first in
        // case the ! sign has space characters in front of it.

        String expression = expressionParam.trim();

        boolean inverseMatching = expression.startsWith("!");

        expression = inverseMatching ? expression.substring(1).trim() : expression;

        // Expression can be comma-separated

        String[] expressionParts = expression.split(",");

        // Start matching

        Boolean matchFound;

        if (inverseMatching) {
            // Inverse matching: return false when a match is found with any of
            // the expression parts
            matchFound = true;

            for (String exprPart : expressionParts) {
                if (value.equals(exprPart.trim())) {
                    matchFound = false;
                }
            }
        } else {
            // Normal matching: return true when a match is found with any of
            // the expression parts

            matchFound = false;

            for (String exprPart : expressionParts) {
                if (value.equals(exprPart.trim())) {
                    matchFound = true;
                }
            }
        }
        return matchFound;
    }

    /**
     * Utility method to fix the url, by convention urls are lower case and all
     * spaces are replaced by dashes (-)
     *
     * @param url , the original url
     */
    public static String fixUrl(String url) {
        return url.replace(' ', '-').toLowerCase();
    }

    /**
     * Renders a list of component presentations.
     *
     * @param request                , the http request
     * @param response               , the http response
     * @param componentPresentations , the list with component presentations
     * @return as string with all component presentations rendered and concatenated.
     */
    public static String renderComponentPresentations(final HttpServletRequest request,
            final HttpServletResponse response, final List<ComponentPresentation> componentPresentations)
            throws FactoryException {
        final StringBuilder output = new StringBuilder();

        if (componentPresentations == null) {
            return output.toString();
        }

        for (ComponentPresentation cp : componentPresentations) {
            output.append(renderComponentPresentation(request, response, cp));
        }

        return output.toString();
    }

    /**
     * Renders a single component presentation using the dispatchers include method.
     *
     * @param request  the http request
     * @param response the http response
     * @param cp       the component presentation
     * @return the rendered component presentation as a string.
     */
    public static String renderComponentPresentation(final HttpServletRequest request,
            final HttpServletResponse response, final ComponentPresentation cp) throws FactoryException {
        return getResponse(request, response, cp, getViewName(cp));
    }

    /**
     * Render a dynamic component presentation based on component / ct uri and view name
     *
     * @param request the current Http Request
     * @param response the current Http Response
     * @param componentURI the Tcm Uri of the component
     * @param templateURI the Tcm Uri of the template
     * @param viewName the view name
     */
    public static String renderDynamicComponentPresentation(final HttpServletRequest request,
            final HttpServletResponse response, final String componentURI, final String templateURI,
            final String viewName) throws FactoryException {
        final ComponentPresentation componentPresentation = ComponentPresentationFactoryImpl.getInstance()
                .getComponentPresentation(componentURI, templateURI);

        List<String> values = new ArrayList<>();
        values.add(viewName);

        TextField field = new TextField();
        field.setName(Constants.VIEW_NAME_FIELD);
        field.setTextValues(values);

        Map<String, Field> metadata = new HashMap<>();
        metadata.put(Constants.VIEW_NAME_FIELD, field);
        componentPresentation.getComponentTemplate().setMetadata(metadata);

        return getResponse(request, response, componentPresentation, viewName);
    }

    /**
     * Dispatch a url to a request dispatcher while buffering the output in a string
     * (and not directly to the response's writer).
     */
    public static String dispatchBufferedRequest(final HttpServletRequest request,
            final HttpServletResponse response, final String url) throws ServletException, IOException {
        long time = System.currentTimeMillis();
        final RequestDispatcher dispatcher = request.getServletContext().getRequestDispatcher(url);
        final HttpServletResponse responseWrapper = new HttpServletResponseWrapper(response) {
            private CharArrayWriter output = new CharArrayWriter();

            @Override
            public String toString() {
                return output.toString();
            }

            @Override
            public PrintWriter getWriter() {
                return new PrintWriter(output);
            }
        };

        dispatcher.include(request, responseWrapper);
        time = System.currentTimeMillis() - time;
        LOG.debug("dispatchBufferedRequest {}, takes: {} ms.", url, time);
        return responseWrapper.toString();
    }

    private static String getResponse(final HttpServletRequest request, final HttpServletResponse response,
            final ComponentPresentation cp, final String viewName) throws FactoryException {
        try {
            final TCMURI tcmuri = new TCMURI(cp.getComponent().getId());
            ComponentUtils.setComponentPresentation(request, cp);

            // TODO: these two might be needed for older implementations
            request.setAttribute(Constants.COMPONENT_TEMPLATE_ID, cp.getComponentTemplate().getId());
            request.setAttribute(Constants.DYNAMIC_COMPONENT_PRESENTATION, cp.isDynamic());
            String url = fixUrl(String.format(Constants.CONTROLLER_MAPPING_PATTERN, viewName, tcmuri.getItemId()));
            setViewModelsOnRequest(request, cp);
            return dispatchBufferedRequest(request, response, url);
        } catch (IOException | ParseException | ServletException e) {
            LOG.error(e.getMessage(), e);
            throw new RenderException(e);
        } finally {
            ComponentUtils.removeComponentPresentation(request);
            removeViewModelsFromRequest(request);
        }
    }

    public static void removeViewModelsFromRequest(final HttpServletRequest request) {
        final ComponentPresentation componentPresentation = ComponentUtils.getComponentPresentation(request);
        if (componentPresentation != null && componentPresentation.getAllViewModels() != null) {
            LOG.debug("Removing STM entries");
            for (final Map.Entry<String, BaseViewModel> modelEntry : componentPresentation.getAllViewModels()
                    .entrySet()) {
                request.removeAttribute(modelEntry.getKey());
            }
        }
    }

    public static void setViewModelsOnRequest(final HttpServletRequest request, final ComponentPresentation cp) {
        final Map<String, BaseViewModel> viewModels = cp.getAllViewModels();
        if (!viewModels.isEmpty()) {
            // Push all STMs on the request stack, so
            // that views can choose which ones to use
            for (final Map.Entry<String, BaseViewModel> modelEntry : viewModels.entrySet()) {
                LOG.debug("Adding model with key: {} and type {} to the request stack", modelEntry.getKey(),
                        modelEntry.getValue());
                request.setAttribute(modelEntry.getKey(), modelEntry.getValue());
            }
        }
    }

    public static void setDynamicComponentOnRequest(final HttpServletRequest request, final Component component) {
        request.setAttribute(Constants.COMPONENT_NAME, component);
    }
}