org.sablo.IndexPageEnhancer.java Source code

Java tutorial

Introduction

Here is the source code for org.sablo.IndexPageEnhancer.java

Source

/*
 * Copyright (C) 2014 Servoy BV
 *
 * 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.sablo;

import java.io.IOException;
import java.io.Writer;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.regex.Matcher;

import org.apache.commons.io.IOUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.sablo.services.template.ModifiablePropertiesGenerator;
import org.sablo.specification.WebComponentPackageSpecification;
import org.sablo.specification.WebComponentSpecProvider;
import org.sablo.specification.WebComponentSpecification;
import org.sablo.specification.WebServiceSpecProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Take an index page, enhance it with required libs/csses and replace variables
 * @author jblok
 */
@SuppressWarnings("nls")
public class IndexPageEnhancer {
    /**
     * Token in html page after which we add component contributions. They have to be before the solution stylesheet.
     */
    private static final String COMPONENT_CONTRIBUTIONS = "<!-- component_contributions -->";

    private static final Logger log = LoggerFactory.getLogger(IndexPageEnhancer.class.getCanonicalName());

    private static String VAR_START = "##";
    private static String VAR_END = "##";

    private IndexPageEnhancer() {
    }

    /**
     * Enhance the provided index.html
     * @param resource url to index.html
     * @param contextPath the path to express in base tag
     * @param cssContributions possible css contributions
     * @param jsContributions possible js contributions
     * @param variableSubstitution replace variables
     * @param writer the writer to write to
     * @throws IOException
     */
    public static void enhance(URL resource, String contextPath, Collection<String> cssContributions,
            Collection<String> jsContributions, Map<String, String> variableSubstitution, Writer writer,
            IContributionFilter contributionFilter) throws IOException {
        String index_file = IOUtils.toString(resource);
        String lowercase_index_file = index_file.toLowerCase();
        int headstart = lowercase_index_file.indexOf("<head>");
        int headend = lowercase_index_file.indexOf(COMPONENT_CONTRIBUTIONS);

        //use real html parser here instead?
        if (variableSubstitution != null) {
            for (String variableName : variableSubstitution.keySet()) {
                String variableReplace = VAR_START + variableName + VAR_END;
                index_file = index_file.replaceAll(Matcher.quoteReplacement(variableReplace),
                        variableSubstitution.get(variableName));
            }
        }

        StringBuilder sb = new StringBuilder(index_file);
        if (headend < 0) {
            log.warn("Could not find marker for component contributions: " + COMPONENT_CONTRIBUTIONS
                    + " for resource " + resource);
        } else {
            sb.insert(headend + COMPONENT_CONTRIBUTIONS.length(),
                    getAllContributions(cssContributions, jsContributions, contributionFilter));
        }
        if (headstart < 0) {
            log.warn("Could not find empty head tag for base tag for resource " + resource);
        } else {
            sb.insert(headstart + 6, getBaseTag(contextPath));
        }
        writer.append(sb);
    }

    /**
     * Get the Base tag to use
     * @param contextPath the contextPath to be used in base tag
     * @return the decorated base tag
     */
    private static String getBaseTag(String contextPath) {
        return String.format("<base href=\"%s/\">\n", contextPath);
    }

    /**
     * Returns the contributions for webcomponents and services
     * @return headContributions
     */
    static String getAllContributions(Collection<String> cssContributions, Collection<String> jsContributions,
            IContributionFilter contributionFilter) {
        ArrayList<String> allCSSContributions = new ArrayList<String>();
        ArrayList<String> allJSContributions = new ArrayList<String>();

        LinkedHashMap<String, JSONObject> allLibraries = new LinkedHashMap<>();
        Collection<WebComponentPackageSpecification<WebComponentSpecification>> webComponentPackagesDescriptions = new ArrayList<WebComponentPackageSpecification<WebComponentSpecification>>();
        webComponentPackagesDescriptions
                .addAll(WebComponentSpecProvider.getInstance().getWebComponentSpecifications().values());
        webComponentPackagesDescriptions
                .addAll(WebServiceSpecProvider.getInstance().getWebServiceSpecifications().values());

        for (WebComponentPackageSpecification<WebComponentSpecification> packageDesc : webComponentPackagesDescriptions) {
            if (packageDesc.getCssClientLibrary() != null) {
                mergeLibs(allLibraries, packageLibsToJSON(packageDesc.getCssClientLibrary(), "text/css"));
            }
            if (packageDesc.getJsClientLibrary() != null) {
                mergeLibs(allLibraries, packageLibsToJSON(packageDesc.getJsClientLibrary(), "text/javascript"));
            }

            for (WebComponentSpecification spec : packageDesc.getSpecifications().values()) {
                allJSContributions.add(spec.getDefinition());
                mergeLibs(allLibraries, spec.getLibraries());
            }
        }

        for (JSONObject lib : allLibraries.values()) {
            switch (lib.optString("mimetype")) {
            case "text/javascript":
                allJSContributions.add(lib.optString("url"));
                break;
            case "text/css":
                allCSSContributions.add(lib.optString("url"));
                break;
            default:
                log.warn("Unknown mimetype " + lib);
            }
        }

        if (cssContributions != null) {
            allCSSContributions.addAll(cssContributions);
        }

        if (jsContributions != null) {
            allJSContributions.addAll(jsContributions);
        }

        StringBuilder retval = new StringBuilder();
        List<String> filteredCSSContributions = contributionFilter != null
                ? contributionFilter.filterCSSContributions(allCSSContributions)
                : allCSSContributions;
        for (String lib : filteredCSSContributions) {
            retval.append(String.format("<link rel=\"stylesheet\" href=\"%s\"/>\n", lib));
        }
        List<String> filteredJSContributions = contributionFilter != null
                ? contributionFilter.filterJSContributions(allJSContributions)
                : allJSContributions;
        for (String lib : filteredJSContributions) {
            retval.append(String.format("<script src=\"%s\"></script>\n", lib));
        }

        // lists properties that need to be watched for client to server changes for each component/service type
        retval.append("<script src=\"spec/").append(ModifiablePropertiesGenerator.PUSH_TO_SERVER_BINDINGS_LIST)
                .append(".js\"></script>\n");

        return retval.toString();
    }

    /**
     * Merge libs into allLibs, by keeping only the lib with the highest version
     * @param allLibs JSONObject list with libraries from all components
     * @param libs JSONObject list with new libraries to add
     */
    private static void mergeLibs(LinkedHashMap<String, JSONObject> allLibs, JSONArray libs) {
        JSONObject lib;
        for (int i = 0; i < libs.length(); i++) {
            lib = libs.optJSONObject(i);
            if (lib != null) {
                String name = lib.optString("name");
                String version = lib.optString("version");
                if (name != null && version != null && lib.has("url") && lib.has("mimetype")) {
                    String key = name + "," + lib.optString("mimetype");
                    JSONObject allLib = allLibs.get(key);
                    if (allLib != null && version.compareTo(allLib.optString("version")) < 0) {
                        log.warn("same lib with lower version found: " + lib);
                        continue;
                    }
                    allLibs.put(key, lib);
                } else {
                    log.warn("Invalid lib description : " + lib);
                }
            }
        }
    }

    private static JSONArray packageLibsToJSON(List<String> packageLibs, String mimeType) {
        JSONArray packageLibsToJSON = new JSONArray();

        try {
            for (String sLib : packageLibs) {
                JSONObject jsonLib = new JSONObject();
                StringTokenizer st = new StringTokenizer(sLib, ";");
                while (st.hasMoreTokens()) {
                    String t = st.nextToken();
                    if (t.startsWith("name=")) {
                        jsonLib.put("name", t.substring(5));
                    } else if (t.startsWith("version=")) {
                        jsonLib.put("version", t.substring(8));
                    } else {
                        jsonLib.put("url", t);
                    }
                }
                jsonLib.put("mimetype", mimeType);
                packageLibsToJSON.put(jsonLib);
            }
        } catch (JSONException ex) {
            log.error("Error converting package lib to json", ex);
        }

        return packageLibsToJSON;
    }
}