it.tidalwave.northernwind.core.impl.filter.XsltMacroFilter.java Source code

Java tutorial

Introduction

Here is the source code for it.tidalwave.northernwind.core.impl.filter.XsltMacroFilter.java

Source

/*
 * #%L
 * *********************************************************************************************************************
 *
 * NorthernWind - lightweight CMS
 * http://northernwind.tidalwave.it - git clone https://bitbucket.org/tidalwave/northernwind-src.git
 * %%
 * Copyright (C) 2011 - 2016 Tidalwave s.a.s. (http://tidalwave.it)
 * %%
 * *********************************************************************************************************************
 *
 * 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.
 *
 * *********************************************************************************************************************
 *
 * $Id$
 *
 * *********************************************************************************************************************
 * #L%
 */
package it.tidalwave.northernwind.core.impl.filter;

import javax.annotation.Nonnull;
import javax.inject.Inject;
import javax.inject.Provider;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.commons.io.IOUtils;
import org.stringtemplate.v4.ST;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Configurable;
import it.tidalwave.northernwind.core.model.ResourceFile;
import it.tidalwave.util.NotFoundException;
import it.tidalwave.northernwind.core.model.Resource;
import it.tidalwave.northernwind.core.model.SiteProvider;
import it.tidalwave.northernwind.core.impl.util.XhtmlMarkupSerializer;
import it.tidalwave.northernwind.core.impl.model.Filter;
import lombok.extern.slf4j.Slf4j;
import static org.springframework.core.Ordered.*;

/***********************************************************************************************************************
 *
 * @author  Fabrizio Giudici
 * @version $Id$
 *
 **********************************************************************************************************************/
@Configurable
@Order(HIGHEST_PRECEDENCE)
@Slf4j
public class XsltMacroFilter implements Filter {
    private static final String XSLT_TEMPLATES_PATH = "/XsltTemplates/.*";

    private static final String DOCTYPE_HTML = "<!DOCTYPE html>";

    @Inject
    private ApplicationContext context;

    @Inject
    private DocumentBuilderFactory factory;

    @Inject
    private TransformerFactory transformerFactory;

    @Inject
    private Provider<SiteProvider> siteProvider;

    private String xslt = "";

    private volatile boolean initialized = false;

    /*******************************************************************************************************************
     *
     ******************************************************************************************************************/
    // FIXME: this should be shared between instances
    private void initialize() throws IOException, NotFoundException {
        log.info("Retrieving XSLT templates");
        final String template = IOUtils.toString(
                getClass().getResourceAsStream("/it/tidalwave/northernwind/core/impl/filter/XsltTemplate.xslt"));
        final StringBuilder xsltBuffer = new StringBuilder();

        for (final Resource resource : siteProvider.get().getSite().find(Resource.class)
                .withRelativePath(XSLT_TEMPLATES_PATH).results()) {
            final ResourceFile file = resource.getFile();
            log.info(">>>> {}", file.getPath().asString());
            xsltBuffer.append(file.asText("UTF-8"));
        }

        final ST t = new ST(template, '%', '%').add("content", xsltBuffer.toString());
        xslt = t.render();
    }

    /*******************************************************************************************************************
     *
     ******************************************************************************************************************/
    @Override
    @Nonnull
    public String filter(final @Nonnull String text, final @Nonnull String mimeType) {
        if (!mimeType.equals("application/xhtml+xml")) {
            log.debug("Cannot filter resources not in XHTML: {}", mimeType);
            return text;
        }

        // FIXME: buggy and cumbersome
        if (!initialized) {
            try {
                synchronized (this) {
                    if (!initialized) {
                        initialize();
                        initialized = true;
                    }
                }
            } catch (IOException | NotFoundException e) {
                throw new RuntimeException(e);
            }
        }

        try {
            final DOMResult result = new DOMResult();
            final Transformer transformer = createTransformer();
            // Fix for NW-100
            transformer.transform(new DOMSource(stringToNode(text.replace("xml:lang", "xml_lang"))), result);

            final StringWriter stringWriter = new StringWriter();

            if (text.startsWith(DOCTYPE_HTML)) {
                stringWriter.append(DOCTYPE_HTML).append("\n");
            }

            // Fix for NW-96
            final XhtmlMarkupSerializer xhtmlSerializer = new XhtmlMarkupSerializer(stringWriter);
            xhtmlSerializer.serialize(result.getNode());
            return stringWriter.toString().replace("xml_lang", "xml:lang").replace(" xmlns=\"\"", ""); // FIXME:
        } catch (SAXParseException e) {
            log.error("XML parse error: {} at l{}:c{}", e.getMessage(), e.getLineNumber(), e.getColumnNumber());
            log.error(text);
            throw new RuntimeException(e);
        } catch (TransformerException e) {
            log.error("XSL error: {} at {}", e.toString(), e.getLocationAsString());
            log.error(xslt);
            throw new RuntimeException(e);
        } catch (IOException | SAXException | ParserConfigurationException e) {
            throw new RuntimeException(e);
        }
    }

    /*******************************************************************************************************************
     *
     ******************************************************************************************************************/
    @Nonnull
    private Transformer createTransformer() throws TransformerConfigurationException {
        final Source transformation = new StreamSource(new StringReader(xslt));
        final Transformer transformer = transformerFactory.newTransformer(transformation);

        try {
            final URIResolver uriResolver = context.getBean(URIResolver.class);
            log.trace("Using URIResolver: {}", uriResolver.getClass());
            transformer.setURIResolver(uriResolver);
        } catch (NoSuchBeanDefinitionException e) {
            // ok, not installed
        }

        return transformer;
    }

    /*******************************************************************************************************************
     *
     ******************************************************************************************************************/
    @Nonnull
    private Node stringToNode(final @Nonnull String string)
            throws IOException, SAXException, ParserConfigurationException {
        factory.setValidating(false);
        final DocumentBuilder builder = factory.newDocumentBuilder();
        final InputSource source = new InputSource(new StringReader(string));
        final Document document = builder.parse(source);
        return document;
    }
}