org.jahia.modules.bootstrap.rules.BootstrapCompiler.java Source code

Java tutorial

Introduction

Here is the source code for org.jahia.modules.bootstrap.rules.BootstrapCompiler.java

Source

/**
 * ==========================================================================================
 * =                   JAHIA'S DUAL LICENSING - IMPORTANT INFORMATION                       =
 * ==========================================================================================
 *
 *     Copyright (C) 2002-2015 Jahia Solutions Group SA. All rights reserved.
 *
 *     THIS FILE IS AVAILABLE UNDER TWO DIFFERENT LICENSES:
 *     1/GPL OR 2/JSEL
 *
 *     1/ GPL
 *     ======================================================================================
 *
 *     IF YOU DECIDE TO CHOSE THE GPL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS:
 *
 *     "This program is free software; you can redistribute it and/or
 *     modify it under the terms of the GNU General Public License
 *     as published by the Free Software Foundation; either version 2
 *     of the License, or (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program; if not, write to the Free Software
 *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *     As a special exception to the terms and conditions of version 2.0 of
 *     the GPL (or any later version), you may redistribute this Program in connection
 *     with Free/Libre and Open Source Software ("FLOSS") applications as described
 *     in Jahia's FLOSS exception. You should have received a copy of the text
 *     describing the FLOSS exception, also available here:
 *     http://www.jahia.com/license"
 *
 *     2/ JSEL - Commercial and Supported Versions of the program
 *     ======================================================================================
 *
 *     IF YOU DECIDE TO CHOOSE THE JSEL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS:
 *
 *     Alternatively, commercial and supported versions of the program - also known as
 *     Enterprise Distributions - must be used in accordance with the terms and conditions
 *     contained in a separate written agreement between you and Jahia Solutions Group SA.
 *
 *     If you are unsure which license is appropriate for your use,
 *     please contact the sales department at sales@jahia.com.
 *
 *
 * ==========================================================================================
 * =                                   ABOUT JAHIA                                          =
 * ==========================================================================================
 *
 *     Rooted in Open Source CMS, Jahias Digital Industrialization paradigm is about
 *     streamlining Enterprise digital projects across channels to truly control
 *     time-to-market and TCO, project after project.
 *     Putting an end to the Tunnel effect?, the Jahia Studio enables IT and
 *     marketing teams to collaboratively and iteratively build cutting-edge
 *     online business solutions.
 *     These, in turn, are securely and easily deployed as modules and apps,
 *     reusable across any digital projects, thanks to the Jahia Private App Store Software.
 *     Each solution provided by Jahia stems from this overarching vision:
 *     Digital Factory, Workspace Factory, Portal Factory and eCommerce Factory.
 *     Founded in 2002 and headquartered in Geneva, Switzerland,
 *     Jahia Solutions Group has its North American headquarters in Washington DC,
 *     with offices in Chicago, Toronto and throughout Europe.
 *     Jahia counts hundreds of global brands and governmental organizations
 *     among its loyal customers, in more than 20 countries across the globe.
 *
 *     For more information, please visit http://www.jahia.com
 */
package org.jahia.modules.bootstrap.rules;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.jahia.api.Constants;
import org.jahia.data.templates.JahiaTemplatesPackage;
import org.jahia.modules.bootstrap.actions.CustomizeBootstrapAction;
import org.jahia.services.content.*;
import org.jahia.services.content.decorator.JCRSiteNode;
import org.jahia.services.content.rules.AddedNodeFact;
import org.jahia.services.templates.JahiaModuleAware;
import org.jahia.services.templates.JahiaTemplateManagerService;
import org.jahia.services.templates.TemplatePackageRegistry;
import org.lesscss.LessCompiler;
import org.lesscss.LessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;

import javax.jcr.NodeIterator;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.RepositoryException;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;
import java.io.*;
import java.util.*;

public class BootstrapCompiler implements JahiaModuleAware {

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

    public static final String CSS_FOLDER_PATH = "files/bootstrap/css";
    public static final String BOOTSTRAP_CSS = "bootstrap.css";
    public static final String BOOTSTRAP_CSS_IMPORT_PATH = "src/main/import/content/modules/%s/" + CSS_FOLDER_PATH
            + "/" + BOOTSTRAP_CSS;
    public static final String LESS_RESOURCES_FOLDER = "less";
    public static final String VARIABLES_LESS = "variables.less";

    private LessCompiler lessCompiler;
    private JahiaTemplateManagerService jahiaTemplateManagerService;
    private JCRPublicationService publicationService;
    private JahiaTemplatesPackage module;

    public void init() {
        if (module == null) {
            return;
        }
        try {
            JCRTemplate.getInstance().doExecuteWithSystemSession(new JCRCallback<Object>() {
                public Object doInJCR(JCRSessionWrapper session) throws RepositoryException {
                    JCRNodeWrapper moduleVersion = session.getNode("/modules/" + module.getIdWithVersion());
                    Resource[] lessResources = module.getResources(LESS_RESOURCES_FOLDER);
                    try {
                        compileBootstrap(moduleVersion, Arrays.asList(lessResources), null);
                    } catch (IOException e) {
                        throw new RepositoryException(e);
                    } catch (LessException e) {
                        throw new RepositoryException(e);
                    }
                    return null;
                }
            });
        } catch (RepositoryException e) {
            log.error("Failed to compile bootstrap.css", e);
        }

    }

    public void compile(final JahiaTemplatesPackage templateSet) throws RepositoryException {
        JCRTemplate.getInstance().doExecuteWithSystemSession(new JCRCallback<Object>() {
            @Override
            public Object doInJCR(JCRSessionWrapper session) throws RepositoryException {
                Resource[] templatesSetLessResources = templateSet.getResources(LESS_RESOURCES_FOLDER);
                if (templatesSetLessResources.length == 0) {
                    // no need to compile bootstrap.css if the templatesSet doesn't contain any less files, just copy it from bootstrap module
                    copyBootstrapCSS(
                            module.getRootFolderPath() + "/" + module.getVersion().toString() + "/"
                                    + CSS_FOLDER_PATH + "/" + BOOTSTRAP_CSS,
                            session.getNode(
                                    templateSet.getRootFolderPath() + "/" + templateSet.getVersion().toString()),
                            session);
                } else {
                    ArrayList<Resource> lessResources = new ArrayList<Resource>(
                            Arrays.asList(templatesSetLessResources));
                    lessResources.addAll(Arrays.asList(module.getResources(LESS_RESOURCES_FOLDER)));
                    try {
                        compileBootstrap(session.getNode(
                                templateSet.getRootFolderPath() + "/" + templateSet.getVersion().toString()),
                                lessResources, null);
                    } catch (IOException e) {
                        log.error(e.getMessage(), e);
                    } catch (LessException e) {
                        log.error(e.getMessage(), e);
                    }
                }

                // copy on all sites using this templateSet, that don't have any custom variables
                QueryManager qm = session.getWorkspace().getQueryManager();
                QueryResult result = qm.createQuery(
                        "SELECT * FROM [jnt:virtualsite] WHERE [j:templatesSet] = '" + templateSet.getId() + "'",
                        Query.JCR_SQL2).execute();
                NodeIterator sites = result.getNodes();
                while (sites.hasNext()) {
                    JCRSiteNode site = (JCRSiteNode) sites.nextNode();
                    if (!site.getAllInstalledModules().contains("bootstrap")
                            || !site.hasNode(CustomizeBootstrapAction.BOOTSTRAP_VARIABLES)) {
                        copyBootstrapCSS(templateSet.getRootFolderPath() + "/" + templateSet.getVersion().toString()
                                + "/" + CSS_FOLDER_PATH + "/" + BOOTSTRAP_CSS, site, session);
                    }
                }
                if (sites.getSize() > 0) {
                    session.save();
                }
                return null;
            }
        });
    }

    private void copyBootstrapCSS(String srcCssPath, JCRNodeWrapper siteOrModuleVersion, JCRSessionWrapper session)
            throws RepositoryException {
        if (session.itemExists(srcCssPath)) {
            JCRNodeWrapper dstCss = siteOrModuleVersion;
            for (String pathPart : StringUtils.split(CSS_FOLDER_PATH, '/')) {
                if (dstCss.hasNode(pathPart)) {
                    dstCss = dstCss.getNode(pathPart);
                } else {
                    dstCss = dstCss.addNode(pathPart, "jnt:folder");
                }
            }
            if (dstCss.hasNode(BOOTSTRAP_CSS)) {
                dstCss.getNode(BOOTSTRAP_CSS).remove();
            }
            session.getNode(srcCssPath).copy(dstCss.getPath());
            session.save();
        }
    }

    public void compileBootstrapWithVariables(JCRSiteNode site, String variables)
            throws RepositoryException, IOException, LessException {
        if (module == null) {
            return;
        }

        Set<JahiaTemplatesPackage> packages = new TreeSet<JahiaTemplatesPackage>(
                TemplatePackageRegistry.TEMPLATE_PACKAGE_COMPARATOR);
        for (String s : site.getInstalledModulesWithAllDependencies()) {
            packages.add(jahiaTemplateManagerService.getTemplatePackageById(s));
        }
        packages.remove(module);
        ArrayList<Resource> lessResources = new ArrayList<Resource>();
        for (JahiaTemplatesPackage aPackage : packages) {
            lessResources.addAll(Arrays.asList(aPackage.getResources(LESS_RESOURCES_FOLDER)));
        }
        lessResources.addAll(Arrays.asList(module.getResources(LESS_RESOURCES_FOLDER)));
        compileBootstrap(site, lessResources, variables);
    }

    private void compileBootstrap(JCRNodeWrapper siteOrModuleVersion, List<Resource> lessResources,
            String variables) throws IOException, LessException, RepositoryException {
        if (lessResources != null && !lessResources.isEmpty()) {
            File tmpLessFolder = new File(FileUtils.getTempDirectory(), "less-" + System.currentTimeMillis());
            tmpLessFolder.mkdir();
            try {
                List<String> allContent = new ArrayList<String>();
                for (Resource lessResource : lessResources) {
                    File lessFile = new File(tmpLessFolder, lessResource.getFilename());
                    if (!lessFile.exists()) {
                        InputStream inputStream;
                        if (variables != null && VARIABLES_LESS.equals(lessResource.getFilename())) {
                            inputStream = new SequenceInputStream(lessResource.getInputStream(),
                                    new ByteArrayInputStream(variables.getBytes()));
                        } else {
                            inputStream = lessResource.getInputStream();
                        }
                        final FileOutputStream output = new FileOutputStream(lessFile);
                        IOUtils.copy(inputStream, output);
                        IOUtils.closeQuietly(inputStream);
                        IOUtils.closeQuietly(output);
                    }
                    final FileInputStream input = new FileInputStream(lessFile);
                    allContent.addAll(IOUtils.readLines(input));
                    IOUtils.closeQuietly(input);
                }
                String md5 = DigestUtils.md5Hex(StringUtils.join(allContent, '\n'));

                JCRNodeWrapper node = siteOrModuleVersion;
                for (String pathPart : StringUtils.split(CSS_FOLDER_PATH, '/')) {
                    if (node.hasNode(pathPart)) {
                        node = node.getNode(pathPart);
                    } else {
                        node = node.addNode(pathPart, "jnt:folder");
                    }
                }

                boolean compileCss = true;
                JCRNodeWrapper bootstrapCssNode;

                if (node.hasNode(BOOTSTRAP_CSS)) {
                    bootstrapCssNode = node.getNode(BOOTSTRAP_CSS);
                    String content = bootstrapCssNode.getFileContent().getText();
                    String timestamp = StringUtils.substringBetween(content, "/* sources hash ", " */");
                    if (timestamp != null && md5.equals(timestamp)) {
                        compileCss = false;
                    }
                } else {
                    bootstrapCssNode = node.addNode(BOOTSTRAP_CSS, "jnt:file");
                }
                if (compileCss) {
                    File bootstrapCss = new File(tmpLessFolder, BOOTSTRAP_CSS);
                    lessCompiler.compile(new File(tmpLessFolder, "bootstrap.less"), bootstrapCss);
                    FileOutputStream f = new FileOutputStream(bootstrapCss, true);
                    IOUtils.write("\n/* sources hash " + md5 + " */\n", f);
                    IOUtils.closeQuietly(f);
                    FileInputStream inputStream = new FileInputStream(bootstrapCss);
                    bootstrapCssNode.getFileContent().uploadFile(inputStream, "text/css");
                    bootstrapCssNode.getSession().save();
                    IOUtils.closeQuietly(inputStream);
                }
            } catch (IOException e) {
                throw new RepositoryException(e);
            } catch (LessException e) {
                throw new RepositoryException(e);
            } finally {
                FileUtils.deleteQuietly(tmpLessFolder);
            }
        }
    }

    public void publish(AddedNodeFact nodeFact) throws RepositoryException {
        publishBootstrapFolder(nodeFact.getNode());
    }

    public void publishBootstrapFolder(JCRNodeWrapper bootstrapFolder) throws RepositoryException {
        List<PublicationInfo> tree = publicationService.getPublicationInfo(bootstrapFolder.getIdentifier(), null,
                true, true, true, Constants.EDIT_WORKSPACE, Constants.LIVE_WORKSPACE);
        publicationService.publishByInfoList(tree, Constants.EDIT_WORKSPACE, Constants.LIVE_WORKSPACE, false,
                new ArrayList<String>());
    }

    public void setLessCompiler(LessCompiler lessCompiler) {
        this.lessCompiler = lessCompiler;
    }

    public void setJahiaTemplateManagerService(JahiaTemplateManagerService jahiaTemplateManagerService) {
        this.jahiaTemplateManagerService = jahiaTemplateManagerService;
    }

    public void setPublicationService(JCRPublicationService publicationService) {
        this.publicationService = publicationService;
    }

    @Override
    public void setJahiaModule(JahiaTemplatesPackage module) {
        this.module = module;
    }
}