com.seajas.search.contender.service.modifier.ModifierScriptProcessor.java Source code

Java tutorial

Introduction

Here is the source code for com.seajas.search.contender.service.modifier.ModifierScriptProcessor.java

Source

/**
 * Copyright (C) 2013 Seajas, the Netherlands.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3, as
 * published by the Free Software Foundation.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */
package com.seajas.search.contender.service.modifier;

import com.seajas.search.bridge.profiler.model.modifier.ModifierScript;
import com.seajas.search.contender.DefaultWebResolver;
import com.seajas.search.contender.WebResolverSettings;
import com.seajas.search.contender.scripting.FeedScript;
import com.seajas.search.contender.scripting.FeedScriptCacheResolver;
import com.seajas.search.contender.scripting.FeedScriptEvaluation;
import com.seajas.search.contender.scripting.ResolvingTransformerCache;
import com.seajas.search.contender.scripting.ScriptCache;
import com.seajas.search.contender.scripting.ScriptCache.ScriptCacheEntry;
import com.seajas.search.contender.scripting.ScriptCacheResolver;
import com.seajas.search.contender.service.ContenderService;
import com.seajas.search.contender.service.cache.CacheService;
import com.seajas.search.utilities.web.WebFeeds;
import com.seajas.search.utilities.web.WebPages;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.SyndFeedOutput;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.URI;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

import javax.script.Bindings;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

/**
 * Process the given modifier.
 *
 * @author Jasper van Veghel <jasper@seajas.com>
 * @author Pascal S. de Kloe <pascal@quies.net>
 */
@Component
public class ModifierScriptProcessor {
    /**
     * The logger.
     */
    private static final Logger logger = LoggerFactory.getLogger(ModifierScriptProcessor.class);

    /**
     * The document builder.
     */
    private final DocumentBuilder documentBuilder;

    /**
     * The transformer cache.
     */
    @Autowired
    private ResolvingTransformerCache transformerCache;

    /**
     * Contender service.
     */
    @Autowired
    private ContenderService contenderService;

    /**
     * Cache service.
     */
    @Autowired
    private CacheService cacheService;

    /**
     * Script manager.
     */
    @Autowired
    private ScriptEngineManager engineManager;

    /**
     * Script cache.
     */
    @Autowired
    private ScriptCache scriptCache;

    /**
     * Default constructor.
     *
     * @throws ParserConfigurationException
     */
    public ModifierScriptProcessor() throws ParserConfigurationException {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();

        documentBuilderFactory.setCoalescing(true);
        documentBuilderFactory.setNamespaceAware(true);

        documentBuilder = documentBuilderFactory.newDocumentBuilder();
    }

    /**
     * Process the given reader using this script.
     *
     * @param script
     * @param input
     * @param uri
     * @param settings
     * @param enableCaching
     * @return Reader
     * @throws IOException
     * @throws ScriptException
     */
    public Reader process(final ModifierScript script, final String input, final URI uri,
            final WebResolverSettings settings, final Boolean enableCaching) throws IOException, ScriptException {
        if (logger.isDebugEnabled()) {
            String msg = "Input for URI '%s' contains %d characters (maximumContentLength = %d, enableCaching = %b)";

            logger.debug(
                    String.format(msg, uri, input.length(), settings.getMaximumContentLength(), enableCaching));

            if (logger.isTraceEnabled())
                logger.trace("Input:  " + input);
        }

        String result = null;

        if (script.getScriptLanguage().equalsIgnoreCase("xslt"))
            result = transformXML(script, input, uri);
        else
            result = evaluateScript(settings, script, input, uri);

        logger.trace(result);

        return new StringReader(result.toString());
    }

    /**
     * Transforms the given Reader content to an XSL-transformed document.
     *
     * @param script
     * @param input
     * @param uri
     * @return String
     * @throws ScriptException
     */
    private String transformXML(final ModifierScript script, final String input, final URI uri)
            throws ScriptException {
        try {
            Transformer transformer = transformerCache.getTransformer(script.getId(), "modifier",
                    script.getModificationDate());

            if (transformer == null)
                transformer = transformerCache.putContent(script.getId(), "modifier", script.getModificationDate(),
                        script.getScriptContent());

            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");

            Node document = documentBuilder.parse(new InputSource(new StringReader(input)));

            StringWriter buffer = new StringWriter();

            transformer.transform(new DOMSource(document), new StreamResult(buffer));

            return buffer.toString();
        } catch (TransformerConfigurationException e) {
            logger.error("Unable to generate a (cached) transformer from the given content", e);

            throw new ScriptException("Unable to generate a (cached) transformer from the given content");
        } catch (Exception e) {
            logger.error("Unable to perform content transformation modifier for " + uri, e);

            throw new ScriptException("Unable to perform content transformation modifier for " + uri);
        }
    }

    /**
     * Evaluate the given script.
     *
     * @param settings
     * @param script
     * @param input
     * @param uri
     * @return String
     * @throws ScriptException
     */
    private String evaluateScript(final WebResolverSettings settings, final ModifierScript script, String input,
            URI uri) throws ScriptException {
        final ScriptCacheEntry entry = scriptCache.acquireScript(script);

        try {
            final ScriptEngine engine = entry != null ? entry.getScript().getEngine()
                    : engineManager.getEngineByName(script.getScriptLanguage().toLowerCase());

            FeedScriptEvaluation evaluation = new FeedScriptEvaluation(settings);
            evaluation.setScriptResolver(new ScriptCacheResolver<FeedScript>() {
                @Override
                public FeedScript resolve(final Bindings bindings) throws ScriptException {
                    for (Map.Entry<String, Object> binding : bindings.entrySet())
                        engine.put(binding.getKey(), binding.getValue());

                    if (entry != null) {
                        if (logger.isTraceEnabled())
                            logger.trace("Executing compiled script with ID " + script.getId());

                        entry.getScript().eval();
                    } else {
                        if (logger.isTraceEnabled())
                            logger.trace(String.format("Executing non-compiled script with ID %d and content '%s'",
                                    script.getId(), script.getScriptContent()));

                        engine.eval(script.getScriptContent());
                    }

                    return ((Invocable) engine).getInterface(FeedScript.class);
                }
            });

            evaluation.setFeedURI(uri);
            evaluation.setFeedContent(input);

            evaluation.setEngine(engine);

            evaluation.setCacheResolver(new FeedScriptCacheResolver() {
                @Override
                public boolean isCached(final String resource) {
                    logger.info("Cache requested URI: " + resource);

                    String cacheKey = cacheService.createCompositeKey(resource, settings.getResultParameters());

                    return cacheService.isCached(cacheKey);
                }
            });

            evaluation.setWebPages(new WebPages());
            DefaultWebResolver resolver = new DefaultWebResolver();
            resolver.setSettings(settings);
            resolver.setContenderService(contenderService);
            evaluation.setWebResolver(resolver);

            evaluation.init();
            evaluation.run();

            SyndFeed feed = evaluation.getFeed();
            if (feed == null)
                throw new ScriptException("No result available");

            try {
                WebFeeds.validate(feed, uri);

                SyndFeedOutput serializer = new SyndFeedOutput();

                return serializer.outputString(feed, true);
            } catch (FeedException e) {
                throw new ScriptException("Can't serialize feed result: " + e);
            }
        } finally {
            scriptCache.releaseScript(script, entry);
        }
    }
}