ome.services.SearchBean.java Source code

Java tutorial

Introduction

Here is the source code for ome.services.SearchBean.java

Source

/*
 *   $Id$
 *
 *   Copyright 2007 Glencoe Software, Inc. All rights reserved.
 *   Use is subject to license terms supplied in LICENSE.txt
 */

package ome.services;

import java.io.Serializable;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import ome.annotations.PermitAll;
import ome.annotations.RolesAllowed;
import ome.api.Search;
import ome.api.ServiceInterface;
import ome.conditions.ApiUsageException;
import ome.conditions.InternalException;
import ome.model.IObject;
import ome.model.annotations.Annotation;
import ome.model.internal.Details;
import ome.parameters.Parameters;
import ome.services.search.AnnotatedWith;
import ome.services.search.Complement;
import ome.services.search.FullText;
import ome.services.search.HqlQuery;
import ome.services.search.Intersection;
import ome.services.search.SearchAction;
import ome.services.search.SearchValues;
import ome.services.search.SimilarTerms;
import ome.services.search.SomeMustNone;
import ome.services.search.TagsAndGroups;
import ome.services.search.Union;
import ome.services.util.Executor;
import ome.system.SelfConfigurableService;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.Analyzer;
import org.springframework.transaction.annotation.Transactional;

/**
 * Implements the {@link Search} interface.
 *
 * @author Josh Moore, josh at glencoesoftware.com
 * @since 3.0-Beta3
 */
@Transactional(readOnly = true)
public class SearchBean extends AbstractStatefulBean implements Search {

    private final static long serialVersionUID = 59809384038000069L;

    /** The logger for this class. */
    private final static Log log = LogFactory.getLog(SearchBean.class);

    private final ActionList actions = new ActionList();

    private final SearchValues values = new SearchValues();

    private final List<List<IObject>> results = new ArrayList<List<IObject>>();

    private/* final */transient Executor executor;

    private/* final */transient Class<? extends Analyzer> analyzer;

    private/* final */transient Integer maxClauseCount;

    public SearchBean(Executor executor, Class<? extends Analyzer> analyzer) {
        this.executor = executor;
        this.analyzer = analyzer;
    }

    public Class<? extends ServiceInterface> getServiceInterface() {
        return Search.class;
    }

    /**
     * Empty constructor required by EJB and
     * {@link SelfConfigurableService self configuration}.
     */
    public SearchBean() {

    }

    /**
     * Injector used by Spring, currently, since
     * {@link SelfConfigurableService#selfConfigure()} requires it.
     */
    public void setExecutor(Executor executor) {
        this.executor = executor;
    }

    /**
     * Injector used by Spring.
     */
    public void setAnalyzer(Class<? extends Analyzer> analyzer) {
        this.analyzer = analyzer;
    }

    /**
     * Injector used by Spring.
     */
    public void setMaxClauseCount(Integer maxClauseCount) {
        this.maxClauseCount = maxClauseCount;
    }

    // Lifecycle methods
    // ===================================================

    // See documentation on JobBean#passivate
    @RolesAllowed("user")
    @Transactional(readOnly = true)
    public void passivate() {
        // All state is passivatable.
    }

    // See documentation on JobBean#activate
    @RolesAllowed("user")
    @Transactional(readOnly = true)
    public void activate() {
        // State needs to be read back with synchronization.
    }

    @RolesAllowed("user")
    @Transactional(readOnly = true)
    public void close() {
        // Could null state.
    }

    // Interface methods ~
    // ============================================

    //
    // CREATE METHODS
    //

    @Transactional
    @RolesAllowed("user")
    public void byAnnotatedWith(Annotation... examples) {
        SearchAction byAnnotatedWith;
        synchronized (values) {
            byAnnotatedWith = new AnnotatedWith(values, examples, false, false);
        }
        actions.add(byAnnotatedWith);
    }

    @Transactional
    @RolesAllowed("user")
    public void byFullText(String query) {
        SearchAction byFullText;
        synchronized (values) {
            byFullText = new FullText(values, query, analyzer);
        }
        actions.add(byFullText);

    }

    @Transactional
    @RolesAllowed("user")
    public void byHqlQuery(String query, Parameters p) {
        SearchAction byHqlQuery;
        synchronized (values) {
            byHqlQuery = new HqlQuery(values, query, p);
        }
        actions.add(byHqlQuery);
    }

    @Transactional
    @RolesAllowed("user")
    public void bySomeMustNone(String[] some, String[] must, String[] none) {
        SearchAction bySomeMustNone;
        synchronized (values) {
            bySomeMustNone = new SomeMustNone(values, some, must, none, analyzer);
        }
        actions.add(bySomeMustNone);
    }

    @Transactional
    @RolesAllowed("user")
    public void bySimilarTerms(String... terms) {
        SearchAction bySimilarTerms;
        synchronized (values) {
            bySimilarTerms = new SimilarTerms(values, terms);
        }
        actions.add(bySimilarTerms);
    }

    @Transactional
    @RolesAllowed("user")
    public void byGroupForTags(String group) {
        SearchAction byTags;
        synchronized (values) {
            byTags = new TagsAndGroups(values, group, false);
        }
        actions.add(byTags);
    }

    @Transactional
    @RolesAllowed("user")
    public void byTagForGroups(String tag) {
        SearchAction byTags;
        synchronized (values) {
            byTags = new TagsAndGroups(values, tag, true);
        }
        actions.add(byTags);
    }

    @Transactional
    @RolesAllowed("user")
    public void byUUID(String[] uuids) {
        throw new UnsupportedOperationException();
    }

    // LOGICAL COMBINATIONS

    @Transactional
    @RolesAllowed("user")
    public void or() {
        actions.union();
    }

    @Transactional
    @RolesAllowed("user")
    public void and() {
        actions.intersection();
    }

    @Transactional
    @RolesAllowed("user")
    public void not() {
        actions.complement();
    }

    //
    // FETCH METHODS
    //

    @Transactional
    @RolesAllowed("user")
    public boolean hasNext() {

        while (results.size() > 0) {
            List<IObject> first = results.get(0);
            if (first == null || first.size() < 1) {
                results.remove(0);
            } else {
                return true;
            }
        }

        // There are no current results, we now need to execute an action
        if (actions.size() == 0) {
            return false;
        }
        SearchAction action = actions.popFirst();
        List<IObject> list = (List<IObject>) executor.execute(null, action);
        results.add(list);
        return hasNext(); // recursive call
    }

    @Transactional
    @RolesAllowed("user")
    public IObject next() throws ApiUsageException {

        if (!hasNext()) {
            throw new ApiUsageException("No element. Please use hasNext().");
        }

        // Now we're guaranteed to have an element
        return pop(results.get(0));
    }

    @Transactional
    @RolesAllowed("user")
    public Map<String, Annotation> currentMetadata() {
        throw new UnsupportedOperationException();
    }

    @Transactional
    @RolesAllowed("user")
    public List<Map<String, Annotation>> currentMetadataList() {
        throw new UnsupportedOperationException();
    }

    @Transactional
    @RolesAllowed("user")
    public <T extends IObject> List<T> results() {

        if (!hasNext()) {
            throw new ApiUsageException("No elements. Please use hasNext().");
        }

        // Now we're guaranteed to have an element
        List<T> rv = new ArrayList<T>();
        while (hasNext() && rv.size() < values.batchSize) {
            List<IObject> current = results.get(0);
            if (current.size() > 0) {
                rv.add((T) pop(current));
            } else {
                // If batches aren't merged, we can exist now.
                if (!values.mergedBatches) {
                    break;
                }
            }
        }
        return rv;
    }

    /**
     * Wrapper method which should be called on all results for the user.
     * Removes the value from the last list, and applies all requirements of
     * {@link #values}.
     */
    protected IObject pop(List<IObject> current) {
        IObject obj = current.remove(0);
        if (values.returnUnloaded) {
            obj.unload();
        }
        return obj;
    }

    @Transactional
    @RolesAllowed("user")
    public void lastresultsAsWorkingGroup() {
        throw new UnsupportedOperationException();
    }

    @Transactional
    @RolesAllowed("user")
    public void remove() throws UnsupportedOperationException {
        throw new UnsupportedOperationException("Cannot remove via ome.api.Search");
    }

    //
    // QUERY MANAGEMENT
    // 

    @Transactional
    @RolesAllowed("user")
    public int activeQueries() {
        return actions.size();
    }

    @Transactional
    @RolesAllowed("user")
    public void clearQueries() {
        actions.clear();
    }

    //
    // TEMPLATE STATE
    //

    @Transactional
    @RolesAllowed("user")
    public void resetDefaults() {
        synchronized (values) {
            values.copy(new SearchValues());
        }
    }

    @Transactional
    @RolesAllowed("user")
    public void addOrderByAsc(String path) {
        synchronized (values) {
            values.orderBy.add("A" + path);
        }
    }

    @Transactional
    @RolesAllowed("user")
    public void addOrderByDesc(String path) {
        synchronized (values) {
            values.orderBy.add("D" + path);
        }
    }

    @Transactional
    @RolesAllowed("user")
    public void unordered() {
        synchronized (values) {
            values.orderBy.clear();
        }
    }

    @Transactional
    @RolesAllowed("user")
    public <T extends IObject> void fetchAlso(Map<T, String> fetches) {
        synchronized (values) {
            throw new UnsupportedOperationException();
        }
    }

    @Transactional
    @RolesAllowed("user")
    public void fetchAnnotations(Class... classes) {
        synchronized (values) {
            values.fetchAnnotations = new ArrayList();
            for (Class k : classes) {
                values.fetchAnnotations.add(k);
            }
        }
    }

    @Transactional
    @RolesAllowed("user")
    public int getBatchSize() {
        synchronized (values) {
            return values.batchSize;
        }
    }

    @Transactional
    @RolesAllowed("user")
    public boolean isCaseSensitive() {
        synchronized (values) {
            return values.caseSensitive;
        }
    }

    @Transactional
    @RolesAllowed("user")
    public boolean isMergedBatches() {
        synchronized (values) {
            return values.mergedBatches;
        }

    }

    @Transactional
    @RolesAllowed("user")
    public void onlyAnnotatedBetween(Timestamp start, Timestamp stop) {
        synchronized (values) {
            values.annotatedStart = SearchValues.copyTimestamp(start);
            values.annotatedStop = SearchValues.copyTimestamp(stop);
        }
    }

    @Transactional
    @RolesAllowed("user")
    public void onlyAnnotatedBy(Details d) {
        synchronized (values) {
            values.annotatedBy = SearchValues.copyDetails(d);
        }
    }

    @Transactional
    @RolesAllowed("user")
    public void notAnnotatedBy(Details d) {
        synchronized (values) {
            values.notAnnotatedBy = SearchValues.copyDetails(d);
        }
    }

    @Transactional
    @RolesAllowed("user")
    public void onlyAnnotatedWith(Class... classes) {
        synchronized (values) {
            if (classes == null) {
                values.onlyAnnotatedWith = null;
            } else {
                List<Class> list = Arrays.<Class>asList(classes);
                values.onlyAnnotatedWith = SearchValues.copyList(list);
            }
        }
    }

    @Transactional
    @RolesAllowed("user")
    public void onlyCreatedBetween(Timestamp start, Timestamp stop) {
        synchronized (values) {
            values.createdStart = SearchValues.copyTimestamp(start);
            values.createdStop = SearchValues.copyTimestamp(stop);
            if (start != null && stop != null) {
                if (stop.getTime() < start.getTime()) {
                    log.warn("FullText search created with " + "creation stop before start");
                }
            }
        }
    }

    @Transactional
    @RolesAllowed("user")
    public void onlyOwnedBy(Details d) {
        synchronized (values) {
            values.ownedBy = SearchValues.copyDetails(d);
        }
    }

    @Transactional
    @RolesAllowed("user")
    public void onlyIds(Long... ids) {
        synchronized (values) {
            if (ids == null) {
                values.onlyIds = null;
            } else {
                values.onlyIds = Arrays.asList(ids);
            }
        }
    }

    @Transactional
    @RolesAllowed("user")
    public void notOwnedBy(Details d) {
        synchronized (values) {
            values.notOwnedBy = SearchValues.copyDetails(d);
        }
    }

    @Transactional
    @RolesAllowed("user")
    public void allTypes() {
        throw new UnsupportedOperationException();
    }

    @Transactional
    @RolesAllowed("user")
    @SuppressWarnings("all")
    public <T extends IObject> void onlyType(Class<T> klass) {
        onlyTypes(klass);
    }

    @Transactional
    @RolesAllowed("user")
    @SuppressWarnings("unchecked")
    public <T extends IObject> void onlyTypes(Class<T>... classes) {
        synchronized (values) {
            values.onlyTypes = new ArrayList();
            for (Class<T> k : classes) {
                values.onlyTypes.add(k);
            }
        }
    }

    @Transactional
    @RolesAllowed("user")
    @SuppressWarnings("unchecked")
    public void setAllowLeadingWildcard(boolean allowLeadingWildcard) {
        synchronized (values) {
            values.leadingWildcard = allowLeadingWildcard;
        }
    }

    @Transactional
    @RolesAllowed("user")
    public void setBatchSize(int size) {
        synchronized (values) {
            values.batchSize = size;
        }
    }

    @Transactional
    @RolesAllowed("user")
    public void setIdOnly() {
        synchronized (values) {
            values.idOnly = true;
        }
    }

    @Transactional
    @RolesAllowed("user")
    public void setMergedBatches(boolean merge) {
        synchronized (values) {
            values.mergedBatches = merge;
        }
    }

    @Transactional
    @RolesAllowed("user")
    public void fetchAlso(String... fetches) {
        synchronized (values) {
            values.fetches = Arrays.asList(fetches);
        }
    }

    @Transactional
    @RolesAllowed("user")
    public boolean isAllowLeadingWildcard() {
        synchronized (values) {
            return values.leadingWildcard;
        }
    }

    @Transactional
    @RolesAllowed("user")
    public boolean isReturnUnloaded() {
        synchronized (values) {
            return values.returnUnloaded;
        }
    }

    @Transactional
    @RolesAllowed("user")
    public boolean isUseProjections() {
        synchronized (values) {
            return values.useProjections;
        }
    }

    @Transactional
    @RolesAllowed("user")
    public void onlyModifiedBetween(Timestamp start, Timestamp stop) {
        synchronized (values) {
            values.modifiedStart = SearchValues.copyTimestamp(start);
            values.modifiedStop = SearchValues.copyTimestamp(stop);
            if (start != null && stop != null) {
                if (stop.getTime() < start.getTime()) {
                    log.warn("FullText search created " + "with modification stop before start");
                }
            }
        }
    }

    @Transactional
    @RolesAllowed("user")
    public void setCaseSentivice(boolean caseSensitive) {
        synchronized (values) {
            values.caseSensitive = caseSensitive;
        }
    }

    @Transactional
    @RolesAllowed("user")
    public void setReturnUnloaded(boolean returnUnloaded) {
        synchronized (values) {
            values.returnUnloaded = returnUnloaded;
        }
    }

    @Transactional
    @RolesAllowed("user")
    public void setUseProjections(boolean useProjections) {
        throw new UnsupportedOperationException();
        // Before activating, please test heavily.
        // In fact, this may need to be removed,
        // since much of the security in Lucene
        // is based on the db.
        // synchronized (values) {
        // values.useProjections = useProjections;
        // }
    }

    //
    // LOCAL API (mostly for testing)
    //

    public void addAction(SearchAction action) {
        if (action == null) {
            throw new IllegalArgumentException("Action cannot be null");
        }
        synchronized (actions) {
            actions.add(action);
        }
    }

    public void addResult(List<IObject> result) {
        synchronized (results) {
            results.add(result); // Can be null as flag?
        }
    }

    /**
     * Synchronized helper collection for maintaining {@link SearchAction}
     * instances. Also knows how to do logical joins (union, etc.)
     */
    private static class ActionList implements Serializable {

        private static final long serialVersionUID = 1L;

        enum State {
            normal, union, intersection, complement;
        }

        private State state = State.normal;

        final private List<SearchAction> actions = new ArrayList<SearchAction>();

        synchronized void union() {
            state = State.union;
        }

        synchronized void intersection() {
            state = State.intersection;
        }

        synchronized void complement() {
            state = State.complement;
        }

        synchronized void add(SearchAction b) {

            // Any call to "add" reset the state of the ActionList
            State previousState = state;
            this.state = State.normal;

            SearchAction a;
            switch (previousState) {
            case normal:
                actions.add(b);
                break;
            case union:
                a = popLast();
                actions.add(new Union(b.copyOfValues(), a, b));
                break;
            case intersection:
                a = popLast();
                actions.add(new Intersection(b.copyOfValues(), a, b));
                break;
            case complement:
                a = popLast();
                actions.add(new Complement(b.copyOfValues(), a, b));
                break;
            default:
                throw new InternalException("Unknown state:" + state);
            }
        }

        synchronized int size() {
            return actions.size();
        }

        synchronized void clear() {
            actions.clear();
        }

        synchronized SearchAction popFirst() {
            assertNonZero();
            return actions.remove(0);
        }

        synchronized SearchAction popLast() {
            assertNonZero();
            return actions.remove(actions.size() - 1);
        }

        synchronized void assertNonZero() {
            if (actions.size() == 0) {
                throw new ApiUsageException("There must be at least 1" + " active query for this operation.");
            }
        }
    }
}