Package org.jboss.dna.graph.search

Source Code of org.jboss.dna.graph.search.SearchableRepositorySource$SynchronousConnection

/*
* JBoss DNA (http://www.jboss.org/dna)
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.  Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
* See the AUTHORS.txt file in the distribution for a full listing of
* individual contributors.
*
* JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
* is licensed to you under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* JBoss DNA 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.dna.graph.search;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.transaction.xa.XAResource;
import net.jcip.annotations.NotThreadSafe;
import org.jboss.dna.common.i18n.I18n;
import org.jboss.dna.common.util.CheckArg;
import org.jboss.dna.common.util.Logger;
import org.jboss.dna.graph.ExecutionContext;
import org.jboss.dna.graph.GraphI18n;
import org.jboss.dna.graph.Subgraph;
import org.jboss.dna.graph.cache.CachePolicy;
import org.jboss.dna.graph.connector.RepositoryConnection;
import org.jboss.dna.graph.connector.RepositoryConnectionFactory;
import org.jboss.dna.graph.connector.RepositoryContext;
import org.jboss.dna.graph.connector.RepositorySource;
import org.jboss.dna.graph.connector.RepositorySourceCapabilities;
import org.jboss.dna.graph.connector.RepositorySourceException;
import org.jboss.dna.graph.observe.Changes;
import org.jboss.dna.graph.observe.Observer;
import org.jboss.dna.graph.request.AccessQueryRequest;
import org.jboss.dna.graph.request.CompositeRequest;
import org.jboss.dna.graph.request.CompositeRequestChannel;
import org.jboss.dna.graph.request.FullTextSearchRequest;
import org.jboss.dna.graph.request.Request;
import org.jboss.dna.graph.request.processor.RequestProcessor;

/**
* A {@link RepositorySource} implementation that can be used as a wrapper around another
* {@link RepositorySourceCapabilities#supportsSearches() non-searchable} or
* {@link RepositorySourceCapabilities#supportsQueries() non-querable} RepositorySource instance to provide search and query
* capability.
*/
public class SearchableRepositorySource implements RepositorySource {

    private static final long serialVersionUID = 1L;

    private final RepositorySource delegate;
    private final boolean executeAsynchronously;
    private final boolean updateIndexesAsynchronously;
    private final transient ExecutorService executorService;
    private final transient SearchEngine searchEngine;

    /**
     * Create a new searchable and queryable RepositorySource around an instance that is neither.
     *
     * @param wrapped the RepositorySource that is not searchable and queryable
     * @param searchEngine the search engine that is to be used
     * @param executorService the ExecutorService that should be used when submitting requests to the wrapped service; may be null
     *        if all operations should be performed in the calling thread
     * @param executeAsynchronously true if an {@link ExecutorService} is provided and the requests to the wrapped source are to
     *        be executed asynchronously
     * @param updateIndexesAsynchronously true if an {@link ExecutorService} is provided and the indexes are to be updated in a
     *        different thread than the thread executing the {@link RepositoryConnection#execute(ExecutionContext, Request)}
     *        calls.
     */
    public SearchableRepositorySource( RepositorySource wrapped,
                                       SearchEngine searchEngine,
                                       ExecutorService executorService,
                                       boolean executeAsynchronously,
                                       boolean updateIndexesAsynchronously ) {
        CheckArg.isNotNull(wrapped, "wrapped");
        CheckArg.isNotNull(searchEngine, "searchEngine");
        this.delegate = wrapped;
        this.executorService = executorService;
        this.searchEngine = searchEngine;
        this.updateIndexesAsynchronously = this.executorService != null && updateIndexesAsynchronously;
        this.executeAsynchronously = this.executorService != null && executeAsynchronously;
    }

    /**
     * Create a new searchable and queryable RepositorySource around an instance that is neither. All of the request processing
     * will be done in the calling thread, and updating the indexes will be done synchronously within the context of the
     * {@link RepositoryConnection#execute(ExecutionContext, Request)} method (and obviously on the same thread). This means that
     * the execution of the requests will not return until the indexes have been updated with any changes made by the requests.
     * <p>
     * This is equivalent to calling <code>new SearchableRepositorySource(wrapped,searchEngine,null,false)</code>
     * </p>
     *
     * @param wrapped the RepositorySource that is not searchable and queryable
     * @param searchEngine the search engine that is to be used
     */
    public SearchableRepositorySource( RepositorySource wrapped,
                                       SearchEngine searchEngine ) {
        this(wrapped, searchEngine, null, false, false);
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.connector.RepositorySource#getName()
     */
    public String getName() {
        return delegate.getName();
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.connector.RepositorySource#close()
     */
    public void close() {
        this.delegate.close();
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.connector.RepositorySource#getCapabilities()
     */
    public RepositorySourceCapabilities getCapabilities() {
        // Return the capabilities of the source, except with search and query suppport enabled ...
        return new RepositorySourceCapabilities(this.delegate.getCapabilities()) {
            /**
             * {@inheritDoc}
             *
             * @see org.jboss.dna.graph.connector.RepositorySourceCapabilities#supportsQueries()
             */
            @Override
            public boolean supportsQueries() {
                return true;
            }

            /**
             * {@inheritDoc}
             *
             * @see org.jboss.dna.graph.connector.RepositorySourceCapabilities#supportsSearches()
             */
            @Override
            public boolean supportsSearches() {
                return true;
            }
        };
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.connector.RepositorySource#getConnection()
     */
    public RepositoryConnection getConnection() throws RepositorySourceException {
        if (executeRequestsAsynchronously()) {
            // Use the executor service ...
            assert executorService != null;
            return new ParallelConnection(executorService);
        }
        // We need to do the processing in this thread ...
        return new SynchronousConnection();
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.connector.RepositorySource#getRetryLimit()
     */
    public int getRetryLimit() {
        return delegate.getRetryLimit();
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.connector.RepositorySource#initialize(org.jboss.dna.graph.connector.RepositoryContext)
     */
    public void initialize( final RepositoryContext context ) throws RepositorySourceException {
        final String delegateSourceName = delegate.getName();

        // The search engine will need a connection factory to the source, but the 'context' connection factory
        // will point back to this wrapper. So make one ...
        final RepositoryConnectionFactory originalConnectionFactory = context.getRepositoryConnectionFactory();
        final RepositoryConnectionFactory connectionFactory = new RepositoryConnectionFactory() {
            /**
             * {@inheritDoc}
             *
             * @see org.jboss.dna.graph.connector.RepositoryConnectionFactory#createConnection(java.lang.String)
             */
            public RepositoryConnection createConnection( String sourceName ) throws RepositorySourceException {
                if (delegateSourceName.equals(sourceName)) return delegate().getConnection();
                return originalConnectionFactory.createConnection(sourceName);
            }
        };

        // Create an observer so that we know what changes are being made in the delegate ...
        final Observer observer = new Observer() {
            /**
             * {@inheritDoc}
             *
             * @see org.jboss.dna.graph.observe.Observer#notify(org.jboss.dna.graph.observe.Changes)
             */
            public void notify( final Changes changes ) {
                if (changes != null) {
                    if (updateIndexesAsynchronously()) {
                        // Enqueue the changes in the delegate content ...
                        executorService().submit(new Runnable() {
                            public void run() {
                                process(context.getExecutionContext(), changes);
                            }
                        });
                    } else {
                        process(context.getExecutionContext(), changes);
                    }
                }
            }
        };

        // Create a new RepositoryContext that uses our observer and connection factory ...
        final RepositoryContext newContext = new RepositoryContext() {
            /**
             * {@inheritDoc}
             *
             * @see org.jboss.dna.graph.connector.RepositoryContext#getConfiguration(int)
             */
            public Subgraph getConfiguration( int depth ) {
                return context.getConfiguration(depth);
            }

            /**
             * {@inheritDoc}
             *
             * @see org.jboss.dna.graph.connector.RepositoryContext#getExecutionContext()
             */
            public ExecutionContext getExecutionContext() {
                return context.getExecutionContext();
            }

            /**
             * {@inheritDoc}
             *
             * @see org.jboss.dna.graph.connector.RepositoryContext#getObserver()
             */
            public Observer getObserver() {
                return observer;
            }

            /**
             * {@inheritDoc}
             *
             * @see org.jboss.dna.graph.connector.RepositoryContext#getRepositoryConnectionFactory()
             */
            public RepositoryConnectionFactory getRepositoryConnectionFactory() {
                return connectionFactory;
            }
        };

        // Now initialize the delegate with the delegate's context ...
        delegate.initialize(newContext);
    }

    protected final SearchEngine searchEngine() {
        assert searchEngine != null;
        return searchEngine;
    }

    protected final boolean updateIndexesAsynchronously() {
        return executorService != null && updateIndexesAsynchronously;
    }

    protected final boolean executeRequestsAsynchronously() {
        return executorService != null && executeAsynchronously;
    }

    protected final ExecutorService executorService() {
        assert executorService != null;
        return executorService;
    }

    protected final RepositorySource delegate() {
        return this.delegate;
    }

    /**
     * Do the work of processing the changes and updating the {@link #searchEngine}. This method may be called while on one of the
     * threads owned by the {@link #executorService executor service} (if {@link #updateIndexesAsynchronously()} returns true), or
     * from the thread {@link RepositoryConnection#execute(ExecutionContext, org.jboss.dna.graph.request.Request) executing} the
     * requests on the {@link #delegate} (if {@link #updateIndexesAsynchronously()} returns false).
     *
     * @param context the execution context in which the indexes should be updated
     * @param changes the changes; never null
     */
    protected void process( ExecutionContext context,
                            Changes changes ) {
        assert context != null;
        assert changes != null;
        if (searchEngine == null) return; // null only after deserialization ...
        // Obtain a request processor
        searchEngine.index(context, changes.getChangeRequests());
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.connector.RepositorySource#setRetryLimit(int)
     */
    public void setRetryLimit( int limit ) {
        delegate.setRetryLimit(limit);
    }

    /**
     * {@inheritDoc}
     *
     * @see javax.naming.Referenceable#getReference()
     */
    public Reference getReference() throws NamingException {
        return delegate.getReference();
    }

    @NotThreadSafe
    protected abstract class AbstractConnection implements RepositoryConnection {
        private RepositoryConnection delegateConnection;

        protected AbstractConnection() {
        }

        protected RepositoryConnection delegateConnection() {
            if (delegateConnection == null) {
                delegateConnection = delegate().getConnection();
            }
            return delegateConnection;
        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.graph.connector.RepositoryConnection#ping(long, java.util.concurrent.TimeUnit)
         */
        public boolean ping( long time,
                             TimeUnit unit ) throws InterruptedException {
            return delegateConnection().ping(time, unit);
        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.graph.connector.RepositoryConnection#getDefaultCachePolicy()
         */
        public CachePolicy getDefaultCachePolicy() {
            return delegateConnection().getDefaultCachePolicy();
        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.graph.connector.RepositoryConnection#getSourceName()
         */
        public String getSourceName() {
            return delegate().getName();
        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.graph.connector.RepositoryConnection#getXAResource()
         */
        public XAResource getXAResource() {
            return delegateConnection().getXAResource();
        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.graph.connector.RepositoryConnection#close()
         */
        public void close() {
            if (delegateConnection != null) {
                try {
                    delegateConnection.close();
                } finally {
                    delegateConnection = null;
                }
            }
        }
    }

    /**
     * A {@link RepositoryConnection} implementation that calls the delegate processor in a background thread, allowing the
     * processing of the {@link FullTextSearchRequest} and {@link AccessQueryRequest} objects to be done in this thread and in
     * parallel with other requests.
     */
    @NotThreadSafe
    protected class ParallelConnection extends AbstractConnection {
        private final ExecutorService executorService;

        protected ParallelConnection( ExecutorService executorService ) {
            this.executorService = executorService;
        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.graph.connector.RepositoryConnection#execute(org.jboss.dna.graph.ExecutionContext,
         *      org.jboss.dna.graph.request.Request)
         */
        public void execute( ExecutionContext context,
                             Request request ) throws RepositorySourceException {
            if (request instanceof AccessQueryRequest) {
                AccessQueryRequest queryRequest = (AccessQueryRequest)request;
                RequestProcessor searchProcessor = searchEngine().createProcessor(context, null, true);
                try {
                    searchProcessor.process(queryRequest);
                } finally {
                    searchProcessor.close();
                }
            } else if (request instanceof FullTextSearchRequest) {
                FullTextSearchRequest searchRequest = (FullTextSearchRequest)request;
                RequestProcessor searchProcessor = searchEngine().createProcessor(context, null, true);
                try {
                    searchProcessor.process(searchRequest);
                } finally {
                    searchProcessor.close();
                }
            } else if (request instanceof CompositeRequest) {
                CompositeRequest composite = (CompositeRequest)request;
                CompositeRequestChannel channel = null;
                RequestProcessor searchProcessor = null;
                try {
                    for (Request nested : composite) {
                        if (nested instanceof AccessQueryRequest) {
                            AccessQueryRequest queryRequest = (AccessQueryRequest)request;
                            if (searchProcessor == null) searchProcessor = searchEngine().createProcessor(context, null, true);
                            searchProcessor.process(queryRequest);
                        } else if (nested instanceof FullTextSearchRequest) {
                            FullTextSearchRequest searchRequest = (FullTextSearchRequest)request;
                            if (searchProcessor == null) searchProcessor = searchEngine().createProcessor(context, null, true);
                            searchProcessor.process(searchRequest);
                        } else {
                            // Delegate to the channel ...
                            if (channel == null) {
                                // Create a connection factory that always returns the delegate connection ...
                                RepositoryConnectionFactory connectionFactory = new RepositoryConnectionFactory() {
                                    /**
                                     * {@inheritDoc}
                                     *
                                     * @see org.jboss.dna.graph.connector.RepositoryConnectionFactory#createConnection(java.lang.String)
                                     */
                                    public RepositoryConnection createConnection( String sourceName )
                                        throws RepositorySourceException {
                                        assert delegate().getName().equals(sourceName);
                                        return delegateConnection();
                                    }
                                };
                                channel = new CompositeRequestChannel(delegate().getName());
                                channel.start(executorService, context, connectionFactory);
                            }
                            channel.add(request);
                        }
                    }
                } finally {
                    try {
                        if (searchProcessor != null) {
                            searchProcessor.close();
                        }
                    } finally {
                        if (channel != null) {
                            try {
                                channel.close();
                            } finally {
                                try {
                                    channel.await();
                                } catch (CancellationException err) {
                                    composite.cancel();
                                } catch (ExecutionException err) {
                                    composite.setError(err);
                                } catch (InterruptedException err) {
                                    // Reset the thread ...
                                    Thread.interrupted();
                                    // Then log the message ...
                                    I18n msg = GraphI18n.interruptedWhileClosingChannel;
                                    Logger.getLogger(getClass()).warn(err, msg, delegate().getName());
                                    composite.setError(err);
                                }
                            }
                        }
                    }
                }
            } else {
                // Just a single, non-query and non-search request ...
                delegateConnection().execute(context, request);
            }
        }
    }

    /**
     * A {@link RepositoryConnection} implementation that calls the delegate processor in the calling thread.
     */
    @NotThreadSafe
    protected class SynchronousConnection extends AbstractConnection {

        protected SynchronousConnection() {
        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.graph.connector.RepositoryConnection#execute(org.jboss.dna.graph.ExecutionContext,
         *      org.jboss.dna.graph.request.Request)
         */
        public void execute( final ExecutionContext context,
                             final Request request ) throws RepositorySourceException {
            if (request instanceof AccessQueryRequest) {
                AccessQueryRequest queryRequest = (AccessQueryRequest)request;
                RequestProcessor searchProcessor = searchEngine().createProcessor(context, null, true);
                try {
                    searchProcessor.process(queryRequest);
                } finally {
                    searchProcessor.close();
                }
            } else if (request instanceof FullTextSearchRequest) {
                FullTextSearchRequest searchRequest = (FullTextSearchRequest)request;
                RequestProcessor searchProcessor = searchEngine().createProcessor(context, null, true);
                try {
                    searchProcessor.process(searchRequest);
                } finally {
                    searchProcessor.close();
                }
            } else if (request instanceof CompositeRequest) {
                CompositeRequest composite = (CompositeRequest)request;
                List<Request> delegateRequests = null;
                RequestProcessor searchProcessor = null;
                try {
                    Request delegateRequest = composite;
                    for (Request nested : composite) {
                        if (nested instanceof AccessQueryRequest) {
                            AccessQueryRequest queryRequest = (AccessQueryRequest)request;
                            if (searchProcessor == null) searchProcessor = searchEngine().createProcessor(context, null, true);
                            searchProcessor.process(queryRequest);
                            delegateRequest = null;
                        } else if (nested instanceof FullTextSearchRequest) {
                            FullTextSearchRequest searchRequest = (FullTextSearchRequest)request;
                            if (searchProcessor == null) searchProcessor = searchEngine().createProcessor(context, null, true);
                            searchProcessor.process(searchRequest);
                            delegateRequest = null;
                        } else {
                            // Delegate the request ...
                            if (delegateRequests == null) {
                                delegateRequests = new LinkedList<Request>();
                            }
                            delegateRequests.add(request);
                        }
                    }
                    if (delegateRequest == null) {
                        // Then there was at least one query or search request ...
                        if (delegateRequests != null) {
                            // There was other requests ...
                            assert !delegateRequests.isEmpty();
                            delegateRequest = CompositeRequest.with(delegateRequests);
                            delegateConnection().execute(context, delegateRequest);
                        } else {
                            // There were no other requests in the composite other than the search and/or query requests ...
                            // So nothing to do ...
                        }
                    } else {
                        // There were no search or query requests, so delegate the orginal composite request ...
                        delegateConnection().execute(context, request);
                    }
                } finally {
                    if (searchProcessor != null) {
                        searchProcessor.close();
                    }
                }
            } else {
                // Just a single, non-query and non-search request ...
                delegateConnection().execute(context, request);
            }
        }
    }
}
TOP

Related Classes of org.jboss.dna.graph.search.SearchableRepositorySource$SynchronousConnection

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.