Package org.carrot2.webapp

Source Code of org.carrot2.webapp.QueryProcessorServlet$UnknownToDefaultTransformer

/*
* Carrot2 project.
*
* Copyright (C) 2002-2014, Dawid Weiss, Stanisław Osiński.
* All rights reserved.
*
* Refer to the full license file "carrot2.LICENSE"
* in the root folder of the repository checkout or at:
* http://www.carrot2.org/carrot2.LICENSE
*/

package org.carrot2.webapp;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.reflect.Field;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.carrot2.core.Controller;
import org.carrot2.core.ControllerFactory;
import org.carrot2.core.ControllerStatistics;
import org.carrot2.core.ProcessingComponentDescriptor;
import org.carrot2.core.ProcessingException;
import org.carrot2.core.ProcessingResult;
import org.carrot2.core.attribute.AttributeNames;
import org.carrot2.source.etools.IpBannedException;
import org.carrot2.text.linguistic.DefaultLexicalDataFactory;
import org.carrot2.util.MapUtils;
import org.carrot2.util.attribute.AttributeBinder;
import org.carrot2.util.attribute.AttributeBinder.IAttributeTransformer;
import org.carrot2.util.attribute.AttributeUtils;
import org.carrot2.util.attribute.Input;
import org.carrot2.util.resource.IResourceLocator;
import org.carrot2.util.resource.PrefixDecoratorLocator;
import org.carrot2.util.resource.ResourceLookup;
import org.carrot2.util.resource.ResourceLookup.Location;
import org.carrot2.util.resource.ServletContextLocator;
import org.carrot2.webapp.jawr.JawrUrlGenerator;
import org.carrot2.webapp.model.AttributeMetadataModel;
import org.carrot2.webapp.model.ModelWithDefault;
import org.carrot2.webapp.model.PageModel;
import org.carrot2.webapp.model.RequestModel;
import org.carrot2.webapp.model.RequestType;
import org.carrot2.webapp.model.ResultsCacheModel;
import org.carrot2.webapp.model.ResultsSizeModel;
import org.carrot2.webapp.model.ResultsViewModel;
import org.carrot2.webapp.model.SkinModel;
import org.carrot2.webapp.model.WebappConfig;
import org.carrot2.webapp.util.UserAgentUtils;
import org.simpleframework.xml.Element;
import org.simpleframework.xml.Root;
import org.simpleframework.xml.core.Persister;
import org.simpleframework.xml.stream.Format;
import org.slf4j.Logger;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
* Processes search requests.
*/
@SuppressWarnings("serial")
public class QueryProcessorServlet extends HttpServlet
{
    /** System property to enable class path search for resources in tests. */
    public final static String ENABLE_CLASSPATH_LOCATOR = "enable.classpath.locator";
   
    /** Controller that performs all searches */
    private transient Controller controller;

    /** Generates urls to combined CSS/Javascript files */
    private transient JawrUrlGenerator jawrUrlGenerator;

    /** {@link #queryLogger} name. */
    static final String QUERY_LOG_NAME = "queryLog";

    /** Query logger. */
    private transient volatile Logger queryLogger = org.slf4j.LoggerFactory.getLogger(QUERY_LOG_NAME);

    /** Error log */
    private transient volatile Logger logger = org.slf4j.LoggerFactory.getLogger(getClass());

    /** A reference to custom log appenders. */
    private transient volatile LogInitContextListener logInitializer;

    /** Global configuration. */
    private WebappConfig webappConfig;

    /** @see UnknownToDefaultTransformer */
    private UnknownToDefaultTransformer unknownToDefaultTransformer;
    private UnknownToDefaultTransformer unknownToDefaultTransformerWithMaxResults;

    /**
     * Define this system property to enable statistical information from the query
     * processor. A GET request to {@link QueryProcessorServlet} with parameter
     * <code>type=STATS</code> and <code>stats.key</code> equal to the value of this
     * property will return plain text information about the processing state.
     */
    public final static String STATS_KEY = "stats.key";

    /** Response constants */
    private final static String UTF8 = "UTF-8";
    private final static String MIME_XML_UTF8 = "text/xml; charset=" + UTF8;
    private final static String MIME_TEXT_PLAIN_UTF8 = "text/plain; charset=" + UTF8;

    /*
     * Servlet lifecycle.
     */
    @Override
    public void init(ServletConfig config) throws ServletException
    {
        super.init(config);

        final ServletContext servletContext = config.getServletContext();

        /*
         * If initialized, custom logging initializer will be here. Save its reference for
         * deferred initialization (for servlet APIs < 2.5).
         */
        logInitializer = (LogInitContextListener) servletContext
            .getAttribute(LogInitContextListener.CONTEXT_ID);

        /*
         * Initialize global configuration and publish it.
         */
        this.webappConfig = WebappConfig.getSingleton(servletContext);
        this.unknownToDefaultTransformer = new UnknownToDefaultTransformer(webappConfig, false);
        this.unknownToDefaultTransformerWithMaxResults = new UnknownToDefaultTransformer(webappConfig, true);

        /*
         * Initialize the controller.
         */
        List<IResourceLocator> locators = Lists.newArrayList();
        locators.add(new PrefixDecoratorLocator(
            new ServletContextLocator(getServletContext()), "/WEB-INF/resources/"));

        if (Boolean.getBoolean(ENABLE_CLASSPATH_LOCATOR))
            locators.add(Location.CONTEXT_CLASS_LOADER.locator);

        controller = ControllerFactory.createCachingPooling(
            ResultsCacheModel.toClassArray(webappConfig.caches));
        controller.init(
            ImmutableMap.<String, Object> of(
                AttributeUtils.getKey(DefaultLexicalDataFactory.class, "resourceLookup"),
                new ResourceLookup(locators)),
            webappConfig.components.getComponentConfigurations());

        jawrUrlGenerator = new JawrUrlGenerator(servletContext);
    }

    /*
     * Servlet lifecycle.
     */
    @Override
    public void destroy()
    {
        if (this.controller != null)
        {
            this.controller.dispose();
            this.controller = null;
        }

        this.jawrUrlGenerator = null;
        this.queryLogger = null;

        super.destroy();
    }

    /*
     * Perform GET request.
     */
    @SuppressWarnings("unchecked")
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        /*
         * Lots of people still use Tomcat 5.5. which has Servlet 2.4 API. Deferred
         * initialization with the now-available context path is here.
         */
        if (logInitializer != null)
        {
            synchronized (this.getClass())
            {
                // Double locking, but the variable is volatile, so o.k.
                if (logInitializer != null)
                {
                    logInitializer.addAppenders(request.getContextPath());
                }
                logInitializer = null;
            }
        }
   
        // Unpack parameters from string arrays
        final Map<String, Object> requestParameters;
        try {
            requestParameters = MapUtils.unpack(request.getParameterMap());
        } catch (Exception e) {
            logger.info("Skipping, could not parse parameters: " + e.toString());
            return;
        }
   
        // Alias "q" to "query" parameter
        final String queryFromAlias =
            (String) requestParameters.get(WebappConfig.QUERY_PARAM_ALIAS);
        if (StringUtils.isNotBlank(queryFromAlias))
        {
            requestParameters.put(WebappConfig.QUERY_PARAM, queryFromAlias);
        }

        // Remove query if blank. This will get the user back to the startup screen.
        final String query = (String) requestParameters.get(WebappConfig.QUERY_PARAM);
        if (StringUtils.isBlank(query))
        {
            requestParameters.remove(WebappConfig.QUERY_PARAM);
        }
        else
        {
            requestParameters.put(WebappConfig.QUERY_PARAM, query.trim());
        }

        final RequestModel requestModel;
        try
        {
            // Build model for this request
            requestModel = new RequestModel(webappConfig);
            requestModel.modern = UserAgentUtils.isModernBrowser(request);
           
            // Request type is normally bound to the model, but we need to know
            // the type before binding to choose the unknown values resolution strategy
            final String requestType = (String)requestParameters.get(WebappConfig.TYPE_PARAM);
           
            final AttributeBinder.AttributeBinderActionBind attributeBinderActionBind =
                new AttributeBinder.AttributeBinderActionBind(
                    requestParameters,
                    true,
                    AttributeBinder.AttributeTransformerFromString.INSTANCE,
                    RequestType.CARROT2DOCUMENTS.name().equals(requestType) ?
                        unknownToDefaultTransformerWithMaxResults :
                        unknownToDefaultTransformer);
            AttributeBinder.bind(requestModel,
                new AttributeBinder.IAttributeBinderAction []
                {
                    attributeBinderActionBind
                }, Input.class);
            requestModel.afterParametersBound(attributeBinderActionBind.remainingValues,
                extractCookies(request));
        }
        catch (Exception e)
        {
            logger.info("Skipping, could not map/bind request model attributes: "
                + e.toString());
            return;
        }

        try
        {
            switch (requestModel.type)
            {
                case STATS:
                    handleStatsRequest(request, response, requestParameters, requestModel);
                    break;

                case ATTRIBUTES:
                    handleAttributesRequest(request, response, requestParameters,
                        requestModel);
                    break;

                case SOURCES:
                    handleSourcesRequest(request, response, requestParameters,
                        requestModel);
                    break;

                default:
                    handleSearchRequest(request, response, requestParameters,
                        requestModel);
            }
        }
        catch (Exception e)
        {
            throw new ServletException(e);
        }
    }

    /**
     * Handles requests for document source attributes.
     *
     * @throws Exception
     */
    private void handleAttributesRequest(HttpServletRequest request,
        HttpServletResponse response, Map<String, Object> requestParameters,
        RequestModel requestModel) throws Exception
    {
        response.setContentType(MIME_XML_UTF8);
        final AjaxAttributesModel model = new AjaxAttributesModel(webappConfig, requestModel);

        final Persister persister = new Persister(getPersisterFormat(requestModel));
        persister.write(model, response.getWriter());
        setExpires(response, 60 * 24 * 7); // 1 week
    }

    /**
     * Required for serialization of attribute metadata combined with a request model.
     */
    @Root(name = "ajax-attribute-metadata")
    private static class AjaxAttributesModel
    {
        @Element(name = "request")
        public final RequestModel requestModel;

        @Element(name = "attribute-metadata")
        public final AttributeMetadataModel attributesModel;

        private AjaxAttributesModel(WebappConfig config, RequestModel requestModel)
        {
            this.requestModel = requestModel;
            this.attributesModel = new AttributeMetadataModel(config);
        }
    }

    /**
     * Handles list of sources requests.
     */
    private void handleSourcesRequest(HttpServletRequest request,
        HttpServletResponse response, Map<String, Object> requestParameters,
        RequestModel requestModel) throws Exception
    {
        response.setContentType(MIME_XML_UTF8);
        final PageModel pageModel = new PageModel(webappConfig, request, requestModel,
            jawrUrlGenerator, null, null);

        final Persister persister = new Persister(
            getPersisterFormat(pageModel.requestModel));
        persister.write(pageModel, response.getWriter());
    }

    /**
     * Handles controller statistics requests.
     */
    private void handleStatsRequest(HttpServletRequest request,
        HttpServletResponse response, Map<String, Object> requestParameters,
        RequestModel requestModel) throws IOException
    {
        final String key = System.getProperty(STATS_KEY);
        if (key != null && key.equals(requestModel.statsKey))
        {
            final ControllerStatistics statistics = controller.getStatistics();

            // Sets encoding for the response writer
            response.setContentType(MIME_TEXT_PLAIN_UTF8);
            final Writer output = response.getWriter();

            output.write("clustering-total-queries: " + statistics.totalQueries + "\n");
            output.write("clustering-good-queries: " + statistics.goodQueries + "\n");

            if (statistics.algorithmTimeAverageInWindow > 0)
            {
                output.write("clustering-ms-per-query: "
                    + statistics.algorithmTimeAverageInWindow + "\n");
                output.write("clustering-updates-in-window: "
                    + statistics.algorithmTimeMeasurementsInWindow + "\n");
            }

            if (statistics.sourceTimeAverageInWindow > 0)
            {
                output.write("source-ms-per-query: "
                    + statistics.sourceTimeAverageInWindow + "\n");
                output.write("source-updates-in-window: "
                    + statistics.sourceTimeMeasurementsInWindow + "\n");
            }

            if (statistics.totalTimeAverageInWindow > 0)
            {
                output.write("all-ms-per-query: " + statistics.totalTimeAverageInWindow + "\n");
                output.write("all-updates-in-window: "
                    + statistics.totalTimeMeasurementsInWindow + "\n");
            }

            output.write("jvm.freemem: " + Runtime.getRuntime().freeMemory() + "\n");
            output.write("jvm.totalmem: " + Runtime.getRuntime().totalMemory() + "\n");

            output.write("cache.hits: " + statistics.cacheHitsTotal + "\n");
            output.write("cache.misses: " + statistics.cacheMisses + "\n");

            output.flush();
        }
        else
        {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
        }
    }

    /**
     * Handles search requests.
     */
    private void handleSearchRequest(HttpServletRequest request,
        HttpServletResponse response, final Map<String, Object> requestParameters,
        final RequestModel requestModel) throws IOException, Exception
    {
        // Add some values in case there was no query parameters -- we want to use the
        // web application defaults and not component defaults.
        requestParameters.put(AttributeNames.RESULTS, requestModel.results);

        // Remove values corresponding to internal attributes
        requestParameters.keySet().removeAll(
            webappConfig.componentInternalAttributeKeys);

        // Perform processing
        ProcessingResult processingResult = null;
        ProcessingException processingException = null;
        try
        {
            if (requestModel.type.requiresProcessing)
            {
                switch (requestModel.type)
                {
                    case CLUSTERS:
                    case FULL:
                    case CARROT2:
                        logQuery(true, requestModel, null);
                        processingResult = controller.process(requestParameters,
                            requestModel.source, requestModel.algorithm);
                        logQuery(false, requestModel, processingResult);
                        break;

                    case DOCUMENTS:
                        processingResult = controller.process(requestParameters,
                            requestModel.source,
                            webappConfig.QUERY_HIGHLIGHTER_ID);
                        break;

                    case CARROT2DOCUMENTS:
                        processingResult = controller.process(requestParameters,
                            requestModel.source);
                        break;

                    default:
                        throw new RuntimeException("Should not reach here.");
                }

                setExpires(response, 5);
            }
        }
        catch (ProcessingException e)
        {
            processingException = e;

            if (e.getCause() instanceof IpBannedException)
            {
                logger.info("Skipping, source IP banned: " + request.getRemoteAddr());
            }
            else
            {
                logger.error("Processing error: " + e.getMessage(), e);
            }
        }

        // Send response, sets encoding of the response writer.
        response.setContentType(MIME_XML_UTF8);

        final Persister persister = new Persister(getPersisterFormat(requestModel));
        final PrintWriter writer = response.getWriter();
        if (RequestType.CARROT2.equals(requestModel.type) ||
            RequestType.CARROT2DOCUMENTS.equals(requestModel.type))
        {
            // Check for an empty processing result.
            if (processingException != null)
            {
                response.sendError(
                    HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                    "Internal server error: " + processingException.getMessage());
                return;
            }

            persister.write(processingResult, writer);
        }
        else
        {
            response.setContentType(MIME_XML_UTF8);

            final PageModel pageModel = new PageModel(webappConfig, request, requestModel,
                jawrUrlGenerator, processingResult, processingException);

            persister.write(pageModel, writer);
        }
    }

    private void logQuery(boolean debug, RequestModel requestModel,
        ProcessingResult processingResult)
    {
        if (debug && !queryLogger.isDebugEnabled()) return;
        if (!queryLogger.isInfoEnabled()) return;

        final String message = requestModel.algorithm
            + ","
            + requestModel.source
            + ","
            + requestModel.results
            + ","
            + (processingResult == null ? "-" : processingResult.getAttributes().get(
                AttributeNames.PROCESSING_TIME_TOTAL)) + "," + requestModel.query;

        if (debug)
            queryLogger.debug(message);
        else
            queryLogger.info(message);
    }

    private void setExpires(HttpServletResponse response, int minutes)
    {
        final HttpServletResponse httpResponse = response;

        final Calendar expiresCalendar = Calendar.getInstance();
        expiresCalendar.add(Calendar.MINUTE, minutes);
        httpResponse.addDateHeader("Expires", expiresCalendar.getTimeInMillis());
    }

    private Format getPersisterFormat(RequestModel requestModel)
    {
        return new Format(2, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
            + "<?ext-stylesheet resource=\""
            + webappConfig.getContextRelativeSkinStylesheet(requestModel) + "\" ?>");
    }

    private Map<String, String> extractCookies(HttpServletRequest request)
    {
        final Map<String, String> result = Maps.newHashMap();
        final Cookie [] cookies = request.getCookies();
        if (cookies != null)
        {
            for (Cookie cookie : cookies)
            {
                result.put(cookie.getName(), cookie.getValue());
            }
        }

        return result;
    }

    /**
     * A transformer that replaces unknown source, document, skin, results and view values
     * to default values.
     */
    private static class UnknownToDefaultTransformer implements IAttributeTransformer
    {
        private final Map<String, Collection<?>> knownValues;
        private final Map<String, Object> defaultValues;

        private final boolean useMaxCarrot2Results;
        private final Integer maxCarrot2Results;
       
        public UnknownToDefaultTransformer(WebappConfig config, boolean useMaxCarrot2Results)
        {
            knownValues = Maps.newHashMap();
            defaultValues = Maps.newHashMap();
           
            this.maxCarrot2Results = config.maxCarrot2Results;
            this.useMaxCarrot2Results = useMaxCarrot2Results;

            // Result sizes
            final Set<Integer> resultSizes = Sets.newHashSet();
            for (ResultsSizeModel size : config.sizes)
            {
                resultSizes.add(size.size);
            }
            knownValues.put(WebappConfig.RESULTS_PARAM, resultSizes);
            defaultValues.put(WebappConfig.RESULTS_PARAM, ModelWithDefault.getDefault(config.sizes).size);

            // Skins
            final Set<String> skinIds = Sets.newHashSet();
            for (SkinModel skin : config.skins)
            {
                skinIds.add(skin.id);
            }
            knownValues.put(WebappConfig.SKIN_PARAM, skinIds);
            defaultValues.put(WebappConfig.SKIN_PARAM, ModelWithDefault.getDefault(config.skins).id);

            // Views
            final Set<String> viewIds = Sets.newHashSet();
            for (ResultsViewModel view : config.views)
            {
                viewIds.add(view.id);
            }
            knownValues.put(WebappConfig.VIEW_PARAM, viewIds);
            defaultValues.put(WebappConfig.VIEW_PARAM, ModelWithDefault.getDefault(config.views).id);

            // Sources
            knownValues.put(WebappConfig.SOURCE_PARAM,
                config.sourceAttributeMetadata.keySet());
            defaultValues.put(WebappConfig.SOURCE_PARAM, config.components.getSources().get(0).getId());

            // Algorithms
            knownValues
                .put(WebappConfig.ALGORITHM_PARAM,
                    Lists.transform(
                            config.components.getAlgorithms(),
                            ProcessingComponentDescriptor.ProcessingComponentDescriptorToId.INSTANCE));
            defaultValues.put(WebappConfig.ALGORITHM_PARAM,
                config.components.getAlgorithms().get(0).getId());
        }

        public Object transform(Object value, String key, Field field)
        {
            final Object defaultValue = defaultValues.get(key);

            if (maxCarrot2Results != null && useMaxCarrot2Results
                && WebappConfig.RESULTS_PARAM.equals(key))
            {
                if (value == null)
                {
                    return defaultValues.get(key);
                }
               
                // Just check if the requested number of results is smaller than
                // the maximum configured for this instance of the webapp
                if (((Integer) value) <= maxCarrot2Results)
                {
                    return value;
                }
                else
                {
                    return maxCarrot2Results;
                }
            }
           
            // Check if we want to handle this attribute at all
            if (defaultValue != null)
            {
                if (knownValues.get(key).contains(value))
                {
                    return value;
                }
                else
                {
                    return defaultValue;
                }
            }
            else
            {
                return value;
            }
        }
    }
}
TOP

Related Classes of org.carrot2.webapp.QueryProcessorServlet$UnknownToDefaultTransformer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.