All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.carrot2.core.PoolingProcessingComponentManager Maven / Gradle / Ivy

Go to download

Carrot2 search results clustering framework. Minimal functional subset (core algorithms and infrastructure, no document sources).

There is a newer version: 3.16.3
Show newest version

/*
 * Carrot2 project.
 *
 * Copyright (C) 2002-2019, 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.core;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.carrot2.core.attribute.Init;
import org.carrot2.core.attribute.Processing;
import org.carrot2.util.ExceptionUtils;
import org.carrot2.util.Pair;
import org.carrot2.util.annotations.ThreadSafe;
import org.carrot2.util.attribute.AttributeBinder;
import org.carrot2.util.attribute.AttributeBinder.AllAnnotationsPresentPredicate;
import org.carrot2.util.attribute.AttributeBinder.BindingTracker;
import org.carrot2.util.attribute.AttributeBinder.IAttributeBinderAction;
import org.carrot2.util.attribute.AttributeBindingException;
import org.carrot2.util.attribute.BindableUtils;
import org.carrot2.util.attribute.Input;
import org.carrot2.util.attribute.Output;
import org.carrot2.util.pool.IActivationListener;
import org.carrot2.util.pool.IDisposalListener;
import org.carrot2.util.pool.IInstantiationListener;
import org.carrot2.util.pool.IParameterizedPool;
import org.carrot2.util.pool.IPassivationListener;
import org.carrot2.util.pool.SoftUnboundedPool;
import org.carrot2.util.resource.IResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.carrot2.shaded.guava.common.base.Predicate;
import org.carrot2.shaded.guava.common.collect.ImmutableSet;
import org.carrot2.shaded.guava.common.collect.Maps;

/**
 * An {@link IProcessingComponentManager} that pools instances of processing components.
 */
public class PoolingProcessingComponentManager implements IProcessingComponentManager
{
    /** Controller context */
    private IControllerContext context;

    /** Pool for component instances. */
    private volatile IParameterizedPool componentPool;

    /** A copy of init attributes */
    private Map initAttributes;

    /** Component configurations, may be empty. */
    private Map componentIdToConfiguration;

    /**
     * Values of {@link Init} {@link Output} attributes collected during initialization.
     * As the {@link #prepare(Class, String, Map, Map)} method is expected to return these
     * and we are borrowing instances from the pool, we need to keep track of
     * initialization results in the ComponentInstantiationListener and pass them back in
     * the create() method.
     */
    private ConcurrentHashMap, String>, Map> initOutputAttributes = new ConcurrentHashMap, String>, Map>();

    /**
     * Creates a new {@link PoolingProcessingComponentManager} using the default pool
     * implementation {@link SoftUnboundedPool}).
     */
    public PoolingProcessingComponentManager()
    {
        this(new SoftUnboundedPool());
    }

    /**
     * Creates a new {@link PoolingProcessingComponentManager} with a custom pool
     * implementation.
     * 
     * @param componentPool the pool to be used by this manager
     */
    public PoolingProcessingComponentManager(
        IParameterizedPool componentPool)
    {
        this.componentPool = componentPool;

        final ComponentResetListener componentResetListener = new ComponentResetListener();
        componentPool.init(new ComponentInstantiationListener(), componentResetListener,
            componentResetListener, ComponentDisposalListener.INSTANCE);
    }

    @Override
    public void init(IControllerContext context, Map attributes,
        ProcessingComponentConfiguration... configurations)
    {
        assert context != null;

        // This will ensure that one manager is used with only one controller
        if (this.context != null)
        {
            throw new IllegalStateException("This manager has already been initialized.");
        }

        this.context = context;
        this.initAttributes = Maps.newHashMap(attributes);
        this.componentIdToConfiguration = ProcessingComponentConfiguration
            .indexByComponentId(configurations);
    }

    @Override
    public IProcessingComponent prepare(Class clazz,
        String id, Map inputAttributes,
        Map outputAttributes)
    {
        try
        {
            final IProcessingComponent component = componentPool.borrowObject(clazz, id);

            // Pass @Init @Output attributes back to the caller.
            final Map initOutputAttrs = initOutputAttributes
                .get(new Pair, String>(component
                    .getClass(), id));
            if (initOutputAttrs != null)
            {
                outputAttributes.putAll(initOutputAttrs);
            }

            return component;
        }
        catch (final InstantiationException e)
        {
            throw new ComponentInitializationException(
                "Could not instantiate component class: " + clazz.getName(), e);
        }
        catch (final IllegalAccessException e)
        {
            throw new ComponentInitializationException(
                "Could not instantiate component class: " + clazz.getName(), e);
        }
    }

    @Override
    public void recycle(IProcessingComponent component, String id)
    {
        componentPool.returnObject(component, id);
    }

    @Override
    public void dispose()
    {
        componentPool.dispose();
    }

    /**
     * Initializes newly created component instances, remembers attribute values so that
     * they can be reset after the component gets returned to the pool.
     */
    private final class ComponentInstantiationListener implements
        IInstantiationListener
    {
        public void objectInstantiated(IProcessingComponent component, String parameter)
        {
            try
            {
                final Map initAttrs = Maps.newHashMap(initAttributes);

                if (parameter != null)
                {
                    initAttrs
                        .putAll(componentIdToConfiguration.get(parameter).attributes);
                }

                final Map initOutputAttrs = Maps.newHashMap();

                checkNonPrimitiveInstances(component, initAttrs,
                    new AllAnnotationsPresentPredicate(Input.class, Init.class));
                ControllerUtils.init(component, initAttrs, initOutputAttrs, false,
                    context);

                // Capture @Init @Output attributes
                initOutputAttributes.putIfAbsent(
                    new Pair, String>(component
                        .getClass(), parameter), 
                        Collections.unmodifiableMap(Maps.newHashMap(initOutputAttrs)));

                // To support a very natural scenario where processing attributes are
                // provided/overridden during initialization, we'll also bind processing
                // attributes here. We need to switch off checking for required attributes
                // as the required processing attributes will be most likely provided at
                // request time. We explicitly skip @Init @Processing attributes here as
                // @Init attributes have already been bound above.
                try
                {
                    final Predicate predicate = new Predicate()
                    {
                        public boolean apply(Field field)
                        {
                            return field.getAnnotation(Input.class) != null
                                && (field.getAnnotation(Processing.class) != null && field
                                    .getAnnotation(Init.class) == null);
                        }
                    };
                    checkNonPrimitiveInstances(component, initAttrs, predicate);
                    AttributeBinder.set(component, initAttrs, false, predicate);
                }
                catch (AttributeBindingException e)
                {
                    throw new ComponentInitializationException(
                        "Could not initialize component", e);
                }
                catch (InstantiationException e)
                {
                    throw new ComponentInitializationException(
                        "Could not initialize component", e);
                }
            }
            catch (Exception e)
            {
                // If init() throws any exception, this exception will
                // be propagated to the borrowObject() call.
                component.dispose();

                throw ExceptionUtils.wrapAs(ComponentInitializationException.class, e);
            }
        }

        /**
         * Performs safety checks aimed at reporting attempts to set non-primitive
         * non-thread-safe primitive instances during initialization. These may lead to
         * hard-to-trace bugs.
         * 
         * @see "https://issues.apache.org/jira/browse/SOLR-2282"
         */
        void checkNonPrimitiveInstances(IProcessingComponent processingComponent,
            Map inputAttributes, Predicate predicate)
            throws InstantiationException
        {
            AttributeBinder.bind(processingComponent, new IAttributeBinderAction []
            {
                new NonPrimitiveInputAttributesCheck(inputAttributes)
            }, predicate);
        }
    }

    /**
     * An {@link IAttributeBinderAction} that checks for non-primitive instances passed
     * for binding at init time. If they are not declared {@link ThreadSafe}, an info is
     * logged.
     */
    static final class NonPrimitiveInputAttributesCheck implements IAttributeBinderAction
    {
        static boolean makeAssertion = false;

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

        static final Set> ALLOWED_PLAIN_TYPES = ImmutableSet.> of(
            Boolean.class, Byte.class, Short.class, Integer.class, Long.class,
            Float.class, Double.class, Character.class, File.class, String.class,
            Calendar.class, Date.class);

        static final Set> ALLOWED_ASSIGNABLE_TYPES = ImmutableSet.> of(
            Enum.class, IResource.class, Collection.class, Map.class);

        // A number of safe typically used classes
        static final Set ALLOWED_PLAIN_TYPES_BY_NAME = ImmutableSet.of(
            "org.apache.lucene.store.FSDirectory",
            "org.apache.lucene.store.RAMDirectory",
            "org.apache.lucene.store.MMapDirectory",
            "org.apache.lucene.store.SimpleFSDirectory",
            "org.apache.lucene.store.SimpleFSDirectory");

        private final Map values;

        NonPrimitiveInputAttributesCheck(Map values)
        {
            this.values = values;
        }

        @Override
        public void performAction(BindingTracker bindingTracker, int level,
            Object object, Field field, Object fieldValue, Predicate predicate)
            throws InstantiationException
        {
            final String key = BindableUtils.getKey(field);
            final Object value = values.get(key);
            if (value == null || Class.class.isInstance(value)
                || Proxy.isProxyClass(value.getClass())
                || value.getClass().getAnnotation(ThreadSafe.class) != null)
            {
                return;
            }

            // If there's an @Init @Input non-primitive attribute whose type
            // is not declared as @ThreadSafe, log a warning.
            final Class valueType = value.getClass();

            if (!ALLOWED_PLAIN_TYPES.contains(valueType)
                && !ALLOWED_PLAIN_TYPES_BY_NAME.contains(valueType.getName())
                && !isAllowedAssignableType(valueType))
            {
                log.info("An object of a non-@ThreadSafe class " + valueType.getName()
                    + " bound at initialization-time to attribute " + key
                    + ". Make sure this is intended.");
                if (makeAssertion)
                {
                    assert false : "An object of a non-@ThreadSafe class "
                        + valueType.getName()
                        + " bound at initialization-time to attribute " + key
                        + ". Make sure this intended.";
                }
            }
        }

        private static boolean isAllowedAssignableType(Class attributeType)
        {
            for (Class clazz : ALLOWED_ASSIGNABLE_TYPES)
            {
                if (clazz.isAssignableFrom(attributeType))
                {
                    return true;
                }
            }

            return false;
        }
    }

    /**
     * Disposes of components on shut down.
     */
    private final static class ComponentDisposalListener implements
        IDisposalListener
    {
        final static ComponentDisposalListener INSTANCE = new ComponentDisposalListener();

        public void dispose(IProcessingComponent component, String parameter)
        {
            component.dispose();
        }
    }

    /**
     * Resets {@link Processing} attribute values before the component is returned to the
     * pool.
     */
    private static final class ComponentResetListener implements
        IPassivationListener,
        IActivationListener
    {
        /**
         * Stores values of {@link Processing} attributes for the duration of processing.
         */
        private ConcurrentHashMap> resetValues = new ConcurrentHashMap>();

        public void activate(IProcessingComponent processingComponent, String parameter)
        {
            // Remember values of @Input @Processing attributes
            final Map originalValues = Maps.newHashMap();
            try
            {
                AttributeBinder.get(processingComponent, originalValues, Input.class,
                    Processing.class);

                resetValues.put(new ReferenceEquality(processingComponent),
                    originalValues);
            }
            catch (Exception e)
            {
                throw new ProcessingException("Could not unbind attribute values", e);
            }
        }

        public void passivate(IProcessingComponent processingComponent, String parameter)
        {
            // Reset values of @Input @Processing attributes back to original values
            try
            {
                // Here's a little hack: we need to disable checking
                // for required attributes, otherwise, we won't be able
                // to reset @Required input attributes to null
                final Map originalAttributes = resetValues
                    .get(new ReferenceEquality(processingComponent));
                if (originalAttributes != null) {
                    AttributeBinder.set(processingComponent, originalAttributes, false,
                        Input.class, Processing.class);
                }
            }
            catch (Exception e)
            {
                throw new ProcessingException("Could not reset attribute values", e);
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy