pt.webdetails.cdf.dd.model.inst.writer.cdfrunjs.dashboard.amd.CdfRunJsDashboardWriter.java Source code

Java tutorial

Introduction

Here is the source code for pt.webdetails.cdf.dd.model.inst.writer.cdfrunjs.dashboard.amd.CdfRunJsDashboardWriter.java

Source

/*!
 * Copyright 2002 - 2016 Webdetails, a Pentaho company. All rights reserved.
 *
 * This software was developed by Webdetails and is provided under the terms
 * of the Mozilla Public License, Version 2.0, or any later version. You may not use
 * this file except in compliance with the license. If you need a copy of the license,
 * please go to http://mozilla.org/MPL/2.0/. The Initial Developer is Webdetails.
 *
 * Software distributed under the Mozilla Public License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. Please refer to
 * the license for the specific language governing your rights and limitations.
 */

package pt.webdetails.cdf.dd.model.inst.writer.cdfrunjs.dashboard.amd;

import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONException;
import pt.webdetails.cdf.dd.CdeConstants;
import static pt.webdetails.cdf.dd.CdeConstants.AmdModule;
import static pt.webdetails.cdf.dd.CdeConstants.RequireJSPlugin;
import static pt.webdetails.cdf.dd.CdeConstants.Writer.*;
import pt.webdetails.cdf.dd.model.core.Thing;
import pt.webdetails.cdf.dd.model.core.UnsupportedThingException;
import pt.webdetails.cdf.dd.model.core.writer.IThingWriteContext;
import pt.webdetails.cdf.dd.model.core.writer.IThingWriter;
import pt.webdetails.cdf.dd.model.core.writer.IThingWriterFactory;
import pt.webdetails.cdf.dd.model.core.writer.ThingWriteException;
import pt.webdetails.cdf.dd.model.core.writer.js.JsWriterAbstract;
import pt.webdetails.cdf.dd.model.inst.Component;
import pt.webdetails.cdf.dd.model.inst.Dashboard;
import pt.webdetails.cdf.dd.model.inst.DataSourceComponent;
import pt.webdetails.cdf.dd.model.inst.writer.cdfrunjs.dashboard.CdfRunJsDashboardWriteContext;
import pt.webdetails.cdf.dd.model.inst.writer.cdfrunjs.dashboard.CdfRunJsDashboardWriteResult;
import pt.webdetails.cdf.dd.render.RenderLayout;
import pt.webdetails.cdf.dd.render.RenderResources;
import pt.webdetails.cdf.dd.render.Renderer;
import pt.webdetails.cdf.dd.render.ResourceMap;
import pt.webdetails.cdf.dd.structure.DashboardWcdfDescriptor;
import pt.webdetails.cdf.dd.util.CdeEnvironment;
import pt.webdetails.cdf.dd.util.Utils;
import pt.webdetails.cpf.Util;

import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Matcher;

public class CdfRunJsDashboardWriter extends JsWriterAbstract implements IThingWriter {
    protected static final Log logger = LogFactory.getLog(CdfRunJsDashboardWriter.class);

    private DashboardWcdfDescriptor.DashboardRendererType type;

    public CdfRunJsDashboardWriter(DashboardWcdfDescriptor.DashboardRendererType type) {
        super();
        this.type = type;
    }

    /**
     * Writes the dashboard to a provided builder object.
     *
     * @param output the builder object to where the processed dashboard will be stored
     * @param context the dashboard context.
     * @param t the dashboard to write.
     * @throws ThingWriteException
     */
    public void write(Object output, IThingWriteContext context, Thing t) throws ThingWriteException {
        this.write((CdfRunJsDashboardWriteResult.Builder) output, (CdfRunJsDashboardWriteContext) context,
                (Dashboard) t);
    }

    /**
     * Writes the dashboard to a provided builder object.
     *
     * @param builder the builder object to where the processed dashboard will be stored
     * @param ctx the dashboard context.
     * @param dash the dashboard to write.
     * @throws ThingWriteException
     */
    public void write(CdfRunJsDashboardWriteResult.Builder builder, CdfRunJsDashboardWriteContext ctx,
            Dashboard dash) throws ThingWriteException {
        assert dash == ctx.getDashboard();

        DashboardWcdfDescriptor wcdf = dash.getWcdf();

        // header
        final String header = ctx.replaceTokens(writeHeaders(dash));

        // content resources
        ResourceMap resources;
        try {
            resources = getResourceRenderer(dash.getLayout("TODO").getLayoutXPContext(), ctx)
                    .renderResources(ctx.getOptions().getAliasPrefix());
        } catch (Exception ex) {
            throw new ThingWriteException("Error rendering resources.", ex);
        }

        // content layout
        final String layout;
        try {
            layout = ctx.replaceTokensAndAlias(this.writeCssCodeResources(resources) + this.writeLayout(ctx, dash));
        } catch (Exception ex) {
            throw new ThingWriteException("Error rendering layout", ex);
        }

        StringBuilder out = new StringBuilder();
        // content dashboard wcdf settings, write WCDF settings
        final String wcdfSettings = writeWcdfSettings(dash);
        // content dashboard components, write component AMD modules and add them to the componentModules map
        final Map<String, String> componentModules = writeComponents(ctx, dash, out);
        final String components = ctx.replaceTokensAndAlias(out.toString());
        // content
        final String content = writeContent(resources, layout, componentModules, wcdfSettings + components, ctx);

        // footer
        final String footer;
        try {
            footer = Util.toString(CdeEnvironment.getPluginSystemReader()
                    .getFileInputStream(CdeConstants.RESOURCE_FOOTER_REQUIRE));
        } catch (IOException ex) {
            throw new ThingWriteException("Could not read footer file.", ex);
        }

        // template
        String template;
        try {
            template = ctx.replaceTokens(Utils.readTemplate(wcdf));
        } catch (IOException ex) {
            throw new ThingWriteException("Could not read style template file.", ex);
        }
        // Leave the DASHBOARD_HEADER_TAG to replace additional stuff on render.
        template = template
                .replaceAll(CdeConstants.DASHBOARD_HEADER_TAG,
                        Matcher.quoteReplacement(header) + CdeConstants.DASHBOARD_HEADER_TAG)
                .replaceAll(CdeConstants.DASHBOARD_CONTENT_TAG, Matcher.quoteReplacement(content))
                .replaceAll(CdeConstants.DASHBOARD_FOOTER_TAG, Matcher.quoteReplacement(footer));

        // Export
        builder.setHeader(header).setLayout(layout).setComponents(components).setContent(content).setFooter(footer)
                .setTemplate(template).setLoadedDate(dash.getSourceDate());
    }

    /**
     * Return a string containing the dashboard HTML layout.
     *
     * @param context the dashboard context
     * @param dash the dashboard to write
     * @return the string containing the dashboard's layout
     */
    protected String writeLayout(CdfRunJsDashboardWriteContext context, Dashboard dash) throws Exception {
        if (dash.getLayoutCount() > 0) {
            JXPathContext docXP = dash.getLayout("TODO").getLayoutXPContext();
            return getLayoutRenderer(docXP, context).render(context.getOptions().getAliasPrefix());
        } else {
            logger.warn("Unable to render layout: no layout found.");
            return "";
        }
    }

    /**
     * Returns a layout renderer that allows to render the dashboard's HTML layout to a string.
     *
     * @param docXP the JXPathContext object
     * @param context the dashboard context
     * @return the generated layout renderer object
     */
    protected Renderer getLayoutRenderer(JXPathContext docXP, CdfRunJsDashboardWriteContext context) {
        return new RenderLayout(docXP, context, false);
    }

    /**
     * Returns a string with the dashboard's CSS code snippet resources contained in HTML style tags.
     *
     * @param resources the dashboard's resources
     * @return the string containing the CSS code snippet resources
     */
    protected String writeCssCodeResources(ResourceMap resources) {

        StringBuilder out = new StringBuilder();
        //CSS
        for (ResourceMap.Resource resource : resources.getCssResources()) {
            if (resource.getResourceType().equals(ResourceMap.ResourceType.CODE)) {
                out.append(resource.getProcessedResource()).append(NEWLINE);
            }
        }

        return out.toString();
    }

    /**
     * Returns a string containing the JavaScript code snippet resources.
     *
     * @param resources the dashboard's resources
     * @return the string containing the processed JavaScript code snippet resources
     */
    protected String writeJsCodeResources(ResourceMap resources) {
        //JS
        StringBuilder sb = new StringBuilder();
        for (ResourceMap.Resource resource : resources.getJavascriptResources()) {
            if (resource.getResourceType().equals(ResourceMap.ResourceType.CODE)) {
                sb.append(resource.getProcessedResource()).append(NEWLINE);
            }
        }

        return sb.toString();
    }

    /**
     * Returns a resource renderer that allows to render the dashboard's resources.
     *
     * @param docXP the JXPathContext object
     * @param context the dashboard context
     * @return the generated resource renderer object
     */
    protected RenderResources getResourceRenderer(JXPathContext docXP, CdfRunJsDashboardWriteContext context) {
        return new RenderResources(docXP, context);
    }

    /**
     * Returns a string containing the dashboard WCDF settings of the provided dashboard.
     *
     * @param dash the dashboard
     * @return the WCDF settings for the provided dashboard
     * @throws ThingWriteException
     */
    protected String writeWcdfSettings(Dashboard dash) throws ThingWriteException {

        StringBuilder wcdfSettings = new StringBuilder();
        DashboardWcdfDescriptor wcdf = dash.getWcdf();

        // Output WCDF
        try {
            return MessageFormat.format(GET_WCDF_SETTINGS_FUNCTION, wcdf.toJSON().toString(6));
        } catch (JSONException ex) {
            throw new ThingWriteException("Converting wcdf to json", ex);
        }
    }

    /**
     * Writes the dashboard components sourcecode to the provided StringBuilder
     * and returns the module ids and class names of the valid component modules found.
     *
     * @param context the dashboard context
     * @param dash the dashboard
     * @param out the StringBuilder to where the components sourcecode will be written
     * @return the Map containing the processed component module ids and class names
     * @throws ThingWriteException
     */
    protected Map<String, String> writeComponents(CdfRunJsDashboardWriteContext context, Dashboard dash,
            StringBuilder out) throws ThingWriteException {

        IThingWriterFactory factory = context.getFactory();
        StringBuilder tmp = new StringBuilder();

        // write data source components
        // store data source
        //tmp.setLength( 0 );
        Iterable<DataSourceComponent> dataSourceComps = dash.getDataSources();
        for (DataSourceComponent comp : dataSourceComps) {
            if (StringUtils.isNotEmpty(comp.getName())) {
                IThingWriter writer;
                try {
                    writer = factory.getWriter(comp);
                    tmp.setLength(0);
                } catch (UnsupportedThingException ex) {
                    throw new ThingWriteException(ex);
                }

                out.append(MessageFormat.format(DASHBOARD_ADD_DATA_SOURCE_INIT, comp.getName()));
                writer.write(tmp, context, comp);
                out.append(MessageFormat.format(DASHBOARD_ADD_DATA_SOURCE_END, tmp.toString()));
            }
        }

        // write regular components
        Map<String, String> componentModules = new LinkedHashMap<String, String>();
        // store component ids
        tmp.setLength(0);
        Iterable<Component> comps = dash.getRegulars();
        for (Component comp : comps) {
            if (StringUtils.isNotEmpty(comp.getName())) {
                IThingWriter writer;
                try {
                    writer = factory.getWriter(comp);
                } catch (UnsupportedThingException ex) {
                    throw new ThingWriteException(ex);
                }

                // custom primitive widget (generic components) & layout component
                if (comp.isVisualComponent()) {
                    if (comp.isCustomComponent() || comp.isPrimitiveComponent()) {
                        String componentClassName = comp.getComponentClassName();

                        if (!componentModules.containsKey(componentClassName)) {
                            String componentModuleId = writeComponentModuleId(comp, componentClassName);
                            if (StringUtils.isEmpty(componentModuleId)) {
                                continue;
                            }
                            componentModules.put(componentClassName, componentModuleId);
                        }
                    }

                    if (tmp.length() > 0) {
                        tmp.append(", ");
                    }
                    tmp.append(comp.getId());
                }

                writer.write(out, context, comp);
            }
        }

        if (tmp.length() > 0) {
            out.append(MessageFormat.format(DASHBOARD_ADD_COMPONENTS, tmp.toString()));
        }

        return componentModules;
    }

    /**
     * Returns a string containing the module id given a component and a class name.
     *
     * @param comp the component used to build the module id
     * @param className the class name used to build the module id
     * @return the string containing the component module id
     */
    protected String writeComponentModuleId(Component comp, String className) {
        StringBuilder componentModuleId = new StringBuilder();

        if (comp.isPrimitiveComponent() && comp.isComponentStaticSystemOrigin()) {

            // CDF component with a static system origin
            componentModuleId.append(CDF_AMD_BASE_COMPONENT_PATH).append(className);

        } else if (comp.isCustomComponent()) {

            if (comp.isComponentStaticSystemOrigin()) {

                // CDE custom component with a static system origin
                componentModuleId.append(CDE_AMD_BASE_COMPONENT_PATH).append(className);

            } else if (comp.isComponentPluginRepositoryOrigin()) {

                String compImplPath = comp.getComponentImplementationPath();

                // if both versions are supported or no implementation path is provided
                // build AMD module using source path and component class name
                if (comp.supportsLegacy() || StringUtils.isEmpty(compImplPath)) {

                    // assume that the AMD implementation file is in the same folder as component.xml
                    // and that it has the same name as the component's class
                    compImplPath = comp.getComponentSourcePath()
                            .split(CdeConstants.CUSTOM_COMPONENT_CONFIG_FILENAME)[0] + className;

                } else {

                    // if it only supports AMD and an implementation path is provided, use it
                    compImplPath = compImplPath.substring(0, compImplPath.lastIndexOf(".js"));
                }

                // validate component's AMD module implementation path
                if (StringUtils.isEmpty(compImplPath)) {
                    logger.error("Missing an implementation code source path for component " + className);
                    return "";
                }

                // CDE custom component uploaded to the repository
                componentModuleId.append(CDE_AMD_REPO_COMPONENT_PATH).append(compImplPath);

            } else if (comp.isComponentOtherPluginStaticSystemOrigin()) {

                // custom component from another plugin (e.g. sparkl)
                componentModuleId.append(comp.getPluginIdFromOrigin()).append(PLUGIN_COMPONENT_FOLDER)
                        .append(className);
            }
        } else if (comp.isWidgetComponent()) {
            // TODO: process WidgetComponent
            logger.error("Unsupported component: " + className);
            return "";
        }

        return componentModuleId.toString();
    }

    /**
     * Returns a string containing the HTML header sourcecode for the given dashboard.
     *
     * @param dash the dashboard object
     * @return the string that contains the dashboard HTML header sourcecode
     */
    protected String writeHeaders(Dashboard dash) {

        return MessageFormat.format(TITLE, dash.getWcdf().getTitle()) + NEWLINE
                + MessageFormat.format(SCRIPT, writeWebcontext("cdf", true));
    }

    /**
     * Returns a string containing the URL that allows to import webcontext into the dashboard's HTML page.
     *
     * @param context the URL context query parameter value
     * @param requireJsOnly the URL requireJsOnly query parameter value
     * @return the string containing the URL to the webcontext including query parameters
     */
    protected String writeWebcontext(String context, boolean requireJsOnly) {
        return MessageFormat.format(WEBCONTEXT, context, Boolean.toString(requireJsOnly).toLowerCase());
    }

    /**
     * Returns the dashboard's generated JavaScript sourcecode.
     *
     * @param resources the dashboard resources
     * @param layout the dashboard HTML layout sourcecode.
     * @param componentModules the dashboard components' AMD modules
     * @param components the dashboard's components
     * @param ctx the dashboard context
     * @return the string containing the dashboard's generated JavaScript sourcecode
     */
    protected String writeContent(ResourceMap resources, String layout, Map<String, String> componentModules,
            String components, CdfRunJsDashboardWriteContext ctx) {

        StringBuilder out = new StringBuilder();

        out.append(layout);

        //do the encapsulation stuff here

        wrapJsScriptTags(out, wrapRequireDefinitions(resources, componentModules, components, ctx));

        return out.toString();
    }

    /**
     * Wraps the JavaScript code, contained in the input parameter, with requirejs configurations.
     *
     * @param resources the dashboard's resources
     * @param componentModules the dashboard's component modules
     * @param content the dashboard generated JavaScript sourcecode to be wrapped
     * @param ctx the dashboard context
     * @return the dashboard's JavaScript sourcecode
     */
    protected String wrapRequireDefinitions(ResourceMap resources, Map<String, String> componentModules,
            String content, CdfRunJsDashboardWriteContext ctx) {

        StringBuilder out = new StringBuilder();

        ArrayList<String> moduleIds = new ArrayList<String>(), moduleClassNames = new ArrayList<String>();

        // Add default dashboard module ids and class names
        addDefaultDashboardModules(moduleIds, moduleClassNames);

        // store component AMD modules ids and class names
        Iterator it = componentModules.entrySet().iterator();
        Map.Entry pair;
        while (it.hasNext()) {
            pair = (Map.Entry) it.next();
            // Add component AMD module path
            moduleIds.add((String) pair.getValue());
            // Add component AMD module class name
            if (!StringUtils.isEmpty((String) pair.getKey())) {
                moduleClassNames.add((String) pair.getKey());
            }
        }

        // write RequireJS module path configurations for JS and CSS file resources
        Map<String, String> fileResourceModules = writeFileResourcesRequireJSPathConfig(out, resources, ctx);

        // Add file resource module ids to the list
        moduleIds.addAll(fileResourceModules.keySet());
        // Add file resource module class names to the list
        moduleClassNames.addAll(fileResourceModules.values());

        // Output module paths and module class names
        writeRequireJsExecutionFunction(out, moduleIds, moduleClassNames);

        //write dashboard declaration
        if (ctx.getOptions().isDebug()) {
            out.append(DASHBOARD_DECLARATION_DEBUG).append(NEWLINE);
        } else {
            out.append(DASHBOARD_DECLARATION).append(NEWLINE);
        }

        // write JS Code snippets
        out.append(writeJsCodeResources(resources));

        // write content
        out.append(content).append(NEWLINE).append(DASHBOARD_INIT).append(REQUIRE_STOP);

        return out.toString();
    }

    /**
     * Adds the default dashboard module ids and class names to the provided lists.
     *
     * @param moduleIds the array list that will hold the default module ids
     * @param moduleClassNames the array list that will hold the default module class names
     */
    protected void addDefaultDashboardModules(ArrayList<String> moduleIds, ArrayList<String> moduleClassNames) {
        final AmdModule dashboardModule = getDashboardModule();
        // Add default module ids
        moduleIds.add(dashboardModule.getId());
        moduleIds.add(AmdModule.LOGGER.getId());
        moduleIds.add(AmdModule.JQUERY.getId());
        moduleIds.add(AmdModule.UNDERSCORE.getId());
        moduleIds.add(AmdModule.MOMENT.getId());
        moduleIds.add(AmdModule.CCC_CDO.getId());
        moduleIds.add(AmdModule.CCC_DEF.getId());
        moduleIds.add(AmdModule.CCC_PV.getId());
        moduleIds.add(AmdModule.CCC_PVC.getId());
        moduleIds.add(AmdModule.UTILS.getId());
        // Add default module class names
        moduleClassNames.add(dashboardModule.getClassName());
        moduleClassNames.add(AmdModule.LOGGER.getClassName());
        moduleClassNames.add(AmdModule.JQUERY.getClassName());
        moduleClassNames.add(AmdModule.UNDERSCORE.getClassName());
        moduleClassNames.add(AmdModule.MOMENT.getClassName());
        moduleClassNames.add(AmdModule.CCC_CDO.getClassName());
        moduleClassNames.add(AmdModule.CCC_DEF.getClassName());
        moduleClassNames.add(AmdModule.CCC_PV.getClassName());
        moduleClassNames.add(AmdModule.CCC_PVC.getClassName());
        moduleClassNames.add(AmdModule.UTILS.getClassName());
    }

    /**
     * Writes the RequireJS 'require' JavaScript function sourcecode to the provided StringBuilder instance.
     *
     * @param out the string builder to where the sourcecode will be written
     * @param ids the array list containing all module ids
     * @param classNames the array list containing all module class names
     */
    protected void writeRequireJsExecutionFunction(StringBuilder out, List<String> ids, List<String> classNames) {
        // remove file resource module empty class names from the list
        Iterator<String> i = classNames.iterator();
        while (i.hasNext()) {
            String className = i.next();
            if (StringUtils.isEmpty(className)) {
                i.remove();
            }
        }
        out.append(MessageFormat.format(REQUIRE_START, StringUtils.join(ids, "'," + NEWLINE + INDENT1 + "'"),
                StringUtils.join(classNames, "," + NEWLINE + INDENT1))).append(NEWLINE);
    }

    /**
     * Returns a string containing the dashboard module id generated using the current dashboard type.
     *
     * @return the dashboard module id
     */
    protected AmdModule getDashboardModule() {
        DashboardWcdfDescriptor.DashboardRendererType dashboardType = getType();
        if (dashboardType.equals(DashboardWcdfDescriptor.DashboardRendererType.BLUEPRINT)) {
            return AmdModule.DASHBOARD_BLUEPRINT;
        } else if (dashboardType.equals(DashboardWcdfDescriptor.DashboardRendererType.BOOTSTRAP)) {
            return AmdModule.DASHBOARD_BOOTSTRAP;
        } else if (dashboardType.equals(DashboardWcdfDescriptor.DashboardRendererType.MOBILE)) {
            return AmdModule.DASHBOARD_MOBILE;
        } else {
            return AmdModule.DASHBOARD_CLEAN;
        }
    }

    /**
     * Returns the current dashboard renderer type.
     *
     * @return the dashboard renderer type
     */
    public DashboardWcdfDescriptor.DashboardRendererType getType() {
        return this.type;
    }

    /**
     * Writes the RequireJS module path configuration sourcecode to a given string builder.
     *
     * @param out the string builder to where the RequireJS module path configuration sourcecode will be written
     * @param resources the dashboard's resources
     * @param ctx the dashboard context
     * @return the Map containing the dashboard resource modules ids and class names
     */
    protected Map<String, String> writeFileResourcesRequireJSPathConfig(StringBuilder out, ResourceMap resources,
            CdfRunJsDashboardWriteContext ctx) {

        Map<String, String> resourceModules = new LinkedHashMap<String, String>();
        // File Resources with empty names should be placed last in the array dependency list
        Map<String, String> unnamedResourceModules = new LinkedHashMap<String, String>();
        String path;
        StringBuilder id = new StringBuilder();

        // JS
        for (ResourceMap.Resource resource : resources.getJavascriptResources()) {
            if (resource.getResourceType().equals(ResourceMap.ResourceType.FILE)) {

                // replace tokens and alias
                path = ctx.replaceTokensAndAlias(resource.getResourcePath());
                // reset
                id.setLength(0);

                // full URI
                if (SCHEME_PATTERN.matcher(path).find()) {

                    if (StringUtils.isEmpty(resource.getResourceName())) {
                        id.append(RESOURCE_AMD_NAMESPACE).append("/").append(getRandomUUID());
                        // store the generated module id and class name to be added at the end of the dependency array
                        unnamedResourceModules.put(id.toString(), resource.getResourceName());
                    } else {
                        id.append(RESOURCE_AMD_NAMESPACE).append("/").append(resource.getResourceName());
                        // store the generated module id and class name
                        resourceModules.put(id.toString(), resource.getResourceName());
                    }

                    // output RequireJS path configuration
                    out.append(MessageFormat.format(REQUIRE_PATH_CONFIG_FULL_URI, id,
                            path.replaceFirst("(?i).js$", ""))).append(NEWLINE);

                } else {

                    // normalize the path
                    path = Util.normalizeUri(path);
                    if (path.startsWith("/")) {
                        path = path.replaceFirst("/", "");
                    }

                    // remove file extension
                    id.append(RESOURCE_AMD_NAMESPACE).append("/").append(path.replaceFirst("(?i).js$", ""));

                    if (StringUtils.isEmpty(resource.getResourceName())) {
                        // store the generated module id and class name to be added at the end of the dependency array
                        unnamedResourceModules.put(id.toString(), resource.getResourceName());
                    } else {
                        // store the generated module id and class name, maintain order
                        resourceModules.put(id.toString(), resource.getResourceName());
                    }

                    // no need for RequireJS path configuration (built based on cde-require-js-cfg.js cde/resources/)
                }
            }
        }

        // CSS
        for (ResourceMap.Resource resource : resources.getCssResources()) {
            if (resource.getResourceType().equals(ResourceMap.ResourceType.FILE)) {

                // replace tokens and alias
                path = ctx.replaceTokensAndAlias(resource.getResourcePath());
                // reset
                id.setLength(0);

                // full URI
                if (SCHEME_PATTERN.matcher(path).find()) {

                    id.append(RESOURCE_AMD_NAMESPACE).append("/")
                            .append((StringUtils.isEmpty(resource.getResourceName()) ? getRandomUUID()
                                    : resource.getResourceName()));
                    // output RequireJS path configuration
                    out.append(MessageFormat.format(REQUIRE_PATH_CONFIG_FULL_URI, id,
                            path.replaceFirst("(?i).css$", ""))).append(NEWLINE);

                } else {

                    // normalize the path
                    path = Util.normalizeUri(path);
                    if (path.startsWith("/")) {
                        path = path.replaceFirst("/", "");
                    }

                    // remove file extension
                    id.append(RESOURCE_AMD_NAMESPACE).append("/").append(path.replaceFirst("(?i).css$", ""));
                    // no need for RequireJS path configuration (built based on cde-require-js-cfg.js cde/resources/)

                }

                // prepend css! RequireJS loader plugin and don't provide a class name for CSS resources
                resourceModules.put(RequireJSPlugin.CSS + id.toString(), "");

            }
        }

        // write require config function call
        if (out.length() > 0) {
            out.append(REQUIRE_CONFIG).append(NEWLINE);
        }

        // Finally append the unnamed JS file resources to be added at the end of the dependency array
        resourceModules.putAll(unnamedResourceModules);

        return resourceModules;
    }

    /**
     * Generates a UUID to be used as part of an AMD module id.
     *
     * @return the string containing the random UUID value
     */
    protected String getRandomUUID() {
        return UUID.randomUUID().toString();
    }

    /**
     * Filters AMD module class names, removing any prepended requireJS loader plugin references from them. Excludes
     * CSS resource modules (file resources). These are used as the input parameters of the require function's callback
     * function parameter.
     *
     * @param resources the dashboard resources
     * @return the list of the JavaScript file resource modules
     */
    protected ArrayList<String> getJsModuleClassNames(ResourceMap resources) {
        ArrayList<String> classNames = new ArrayList<String>();
        String className;
        // Filter and remove known RequireJS Loader plugin from class names
        // JS
        for (ResourceMap.Resource resource : resources.getJavascriptResources()) {
            if (resource.getResourceType().equals(ResourceMap.ResourceType.FILE)) {
                if (StringUtils.isEmpty(className = getModuleClassName(resource.getResourceName()))) {
                    continue;
                }
                classNames.add(className);
            }
        }

        // CSS AMD modules are not included as input parameters in require function's callback function parameter

        return classNames;
    }

    /**
     * Filters an AMD module class name, removing any prepended RequireJS loader plugin references from it.
     *
     * @param className the unfiltered module class name
     * @return the string containing the filtered module class name
     */
    protected String getModuleClassName(String className) {
        if (StringUtils.isEmpty(className)) {
            return "";
        }
        // remove prepended requireJS loader plugins and resource namespace from class name
        for (RequireJSPlugin plugin : RequireJSPlugin.values()) {
            className = className.replace(plugin.toString(), "").replace(RESOURCE_AMD_NAMESPACE + "/", "");
        }

        return className;
    }
}