/*
 * $Header: /home/cvspublic/jakarta-slide/src/share/org/apache/slide/search/basic/BasicQuery.java,v 1.21 2004/07/28 09:35:02 ib Exp $
 * $Revision: 1.21 $
 * $Date: 2004/07/28 09:35:02 $
 *
 * ====================================================================
 *
 * Copyright 1999-2002 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.apache.slide.search.basic;

import org.apache.slide.common.PropertyParseException;
import org.apache.slide.common.RequestedProperties;
import org.apache.slide.common.RequestedPropertiesImpl;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.Uri;
import org.apache.slide.search.BadQueryException;
import org.apache.slide.search.InvalidScopeException;
import org.apache.slide.search.PropertyProvider;
import org.apache.slide.search.QueryScope;
import org.apache.slide.search.SearchQuery;
import org.apache.slide.search.SearchQueryResult;
import org.apache.slide.search.SearchToken;
import org.apache.slide.search.SlideUri;
import org.apache.slide.store.AbstractStore;
import org.jdom.Element;
import org.jdom.Namespace;

/**
 * A BasicQuery represents the query and is able to deliver a
 * SearchQueryResult using the execute method. It serves as a base class for
 * store specific implementations. It hosts the information about the SELECT,
 * FROM, WHERE, ORDERBY and LIMIT. It also holds a tree of
 * BasicSearchExpressions.
 *
 * @version $Revision: 1.21 $
 */
public abstract class BasicQuery extends SearchQuery implements IBasicQuery {
    
    /**
     * Message of a BadQueryException that is thrown if the query element
     * is <code>null</code>.
     */
    public static final String NO_QUERY_ELEMENT = "No query element";
    
    /**
     * The provider which delivers the expression compiler to use.
     */
    protected IBasicExpressionCompilerProvider expressionCompilerProvider = null;
    
    /**
     * Message of a BadQueryException that is thrown if the query element
     * does not contain a &lt;select&gt; element.
     */
    public static final String SELECT_ELEMENT_MISSING = "Required element <select> not supplied";
    
    /**
     * Message of a BadQueryException that is thrown if the query element
     * does not contain a &lt;from&gt; element.
     */
    public static final String FROM_ELEMENT_MISSING = "Required element <from> not supplied";
    
    /**
     * Message of a BadQueryException that is thrown if the query element
     * neither contains a &lt;prop&gt; nor a &lt;allprop&gt; element.
     */
    public static final String PROP_OR_ALLPROP_ELEMENT_MISSING = "Required element <prop> or <allprop> not supplied";
    
    private IBasicExpressionFactory contentExpressionFactory;
    private IBasicExpressionFactory propertiesExpressionFactory;
    
    
    /** the element describing this query */
    protected Element queryElement;
    
    /** the namespace for this query */
    protected Namespace namespace;
    
    /** the scope of this query, <FROM> */
    protected QueryScope queryScope;
    
    /** the element describing the WHERE clauise */
    protected Element whereElement;
    
    /** List of requested properties, <SELECT> */
    protected RequestedProperties requestedProperties;
    
    /** <LIMIT> */
    protected int limit;
    
    /** ORDER BY */
    protected OrderBy orderBy;
    
    /** indicates, if a limit is defined */
    protected boolean limitDefined = false;
    
    /** The store for this query, may be used to access store parameters */
    protected AbstractStore store;
    
    /** the top level expression in the <WHERE> clause */
    protected IBasicExpression rootExpression;
    
    /** used to get the slidePath */
    protected SlideUri slideUri;
    
    /** The provider for the properties */
    protected PropertyProvider propertyProvider;
    
    
    protected BasicQuery () {}
    
    protected BasicQuery (SearchToken searchToken) {
        init(searchToken);
    }
    
    public void init (SearchToken token) {
        this.searchToken = token;
        slideUri = searchToken.getSlideContext();
        this.expressionCompilerProvider = new ExpressionCompilerProvider();
    }
    
    
    /**
     * returns the factory for content expressions. May be null, if store does
     * not provide search capabilities and no indexer is defined.
     *
     * @return   an IBasicExpressionFactory
     *
     */
    public IBasicExpressionFactory getContentExpressionFactory ()
    {
        if (contentExpressionFactory == null)
            contentExpressionFactory =
                store.getContentIndexer().getBasicExpressionFactory();
        
        return contentExpressionFactory;
    }
    
    
    /**
     * returns the factory for property expressions. May be null, if store does
     * not provide search capabilities and no indexer is defined.
     *
     * @return   an IBasicExpressionFactory
     *
     */
    public IBasicExpressionFactory getPropertiesExpressionFactory ()
    {
        propertiesExpressionFactory =
            store.getPropertiesIndexer().getBasicExpressionFactory();
        
        return propertiesExpressionFactory;
    }
    
    
    /**
     * Method getStore
     *
     * @return   an AbstractStore
     *
     */
    public AbstractStore getStore () {
        return store;
    }
    
    /**
     * Method getSlidePath
     *
     * @return   a String
     *
     * @throws   InvalidScopeException
     *
     */
    public String getSlidePath () throws InvalidScopeException {
        return slideUri.getSlidePath (queryScope.getHref());
    }
    
    /**
     * Method getSearchToken
     *
     * @return   a SearchToken
     *
     */
    public SearchToken getSearchToken (){
        return searchToken;
    }
    
    /**
     * Method getPropertyProvider
     *
     * @return   a PropertyProvider
     *
     */
    public PropertyProvider getPropertyProvider () {
        return propertyProvider;
    }
    
    /**
     * Builds the internal structure from the JDOM tree. Concrete implementations
     * may use parseQueryElementWithoutExpression to create most of the
     * objects describing the query.
     *
     * @param    basicSearchElement  the (root) expression Element.
     * @param    propertyProvider    the PropertyProvider to use (may be
     *                               <code>null</code>).
     *
     * @throws   BadQueryException
     */
    public void parseQueryElement (Element basicSearchElement, PropertyProvider propertyProvider) throws BadQueryException {
        
        queryScope = getScope(basicSearchElement);
        this.propertyProvider = propertyProvider;
        
        // might be null in testsuite
        if (searchToken.getNamespace() != null) {
            //          Uri uri = new Uri (searchToken.getNamespace(), slideUri.getSlidePath(queryScope.getHref()));
            Uri uri = searchToken.getNamespace().getUri(this.getSearchToken().getSlideToken(), slideUri.getSlidePath(queryScope.getHref()));
            store = (AbstractStore)uri.getStore();
        }
        
        parseQuery(basicSearchElement, propertyProvider);
    }
    
    /**
     * builds the internal structure from the JDOM tree. Concrete implementations
     * may use {@link #parseQueryWithoutExpression parseQueryWithoutExpression}
     * to create most of the objects describing the query.
     *
     * @param    basicSearchElement  the (root) expression Element.
     * @param    propertyProvider    the PropertyProvider to use (may be
     *                               <code>null</code>).
     *
     * @throws   BadQueryException
     */
    public abstract void parseQuery (Element basicSearchElement, PropertyProvider propertyProvider)
        throws BadQueryException;
    
    /**
     * Executes a request. A store specific implementation should overwrite
     * this to optimize the execution.
     *
     * @return   a SearchQueryResult
     *
     * @throws   ServiceAccessException
     *
     */
    public abstract SearchQueryResult execute () throws ServiceAccessException;
    
    /**
     * builds the internal structure from the JDOM tree. It may be used by the
     * concrete implementation of BasicQuery. It does NOT create the tree of
     * Expressions. This must be done in the specific implementation.
     *
     * @param    basicSearchElement                an Element
     *
     * @throws   BadQueryException
     */
    protected void parseQueryWithoutExpression (Element basicSearchElement) throws BadQueryException {
        
        if (basicSearchElement == null)
            throw new BadQueryException (NO_QUERY_ELEMENT);
        
        namespace = basicSearchElement.getNamespace();
        
        Element selectElement = basicSearchElement.getChild
            (Literals.SELECT, namespace);
        
        // SELECT is mandatory
        if (selectElement == null)
            throw new BadQueryException (SELECT_ELEMENT_MISSING);
        
        Element fromElement = basicSearchElement.getChild
            (Literals.FROM, namespace);
        
        // FROM is mandatory
        if (fromElement == null) {
            throw new BadQueryException (FROM_ELEMENT_MISSING);
        }
        
        whereElement = basicSearchElement.getChild
            (Literals.WHERE, namespace);
        
        Element orderByElement = basicSearchElement.getChild
            (Literals.ORDERBY, namespace);
        
        Element limitElement = basicSearchElement.getChild
            (Literals.LIMIT, namespace);
        
        Element propElement = selectElement.getChild (Literals.PROP, namespace);
        if (propElement == null) {
            propElement = selectElement.getChild (Literals.ALLPROP, namespace);
        }
        
        if (propElement == null) {
            throw new BadQueryException(PROP_OR_ALLPROP_ELEMENT_MISSING);
        }
        
        try {
            requestedProperties = new RequestedPropertiesImpl (propElement);
        }
        catch (PropertyParseException e) {
            throw new BadQueryException(e.getMessage(), e);
        }
        
        queryScope = new BasicQueryScope (fromElement);
        
        if (orderByElement != null) {
            orderBy = new OrderBy ();
            orderBy.init (orderByElement);
        }
        
        if (limitElement != null) {
            limit = new Integer (limitElement.getTextTrim()).intValue();
            limitDefined = true;
        }
    }
    
    /**
     * QueryScope accessor
     *
     * @return   the Scope
     *
     */
    public QueryScope getScope () {
        return queryScope;
    }
    
    /**
     * Method getSelectedProperties
     *
     * @return   a SelectedPropertyList
     */
    public RequestedProperties requestedProperties () {
        return requestedProperties;
    }
    
    /**
     * Method getExpression
     *
     * @return   a BasicExpression
     *
     */
    public IBasicExpression getExpression () {
        return rootExpression;
    }
    
    
    /**
     * Method isLimitDefined
     *
     * @return true if <limit> was specified
     */
    public boolean isLimitDefined () {
        return limitDefined;
    }
    
    /**
     * Method getLimit
     *
     * @return   the value of <limit>
     */
    public int getLimit () {
        return limit;
    }
    
    /**
     * Method setScope
     *
     * @param    queryScope          a  QueryScope
     *
     */
    public void setScope (QueryScope queryScope) {
        this.queryScope = queryScope;
    }
    
    /**
     * Method getOrderBy
     *
     * @return   an OrderBy
     *
     */
    public OrderBy getOrderBy () {
        return orderBy;
    }
    
    
    /**
     * For debugging purpose.
     *
     * @return   this query in string representation
     *
     */
    public String toString () {
        
        String result =
            "SELECT [" + requestedProperties + "] FROM [" + queryScope + "] "
            + "WHERE [" + rootExpression + "]";
        
        return result;
    }
    
    /**
     * Needed to decide, which implementation of BasicQuery to load
     *
     * @param    basicSearchElementJDOM an Element
     *
     * @return   a QueryScope
     *
     * @throws   BadQueryException
     *
     */
    public static QueryScope getScope(Element basicSearchElementJDOM)
        throws BadQueryException
    {
        if (basicSearchElementJDOM == null)
            throw new BadQueryException (NO_QUERY_ELEMENT);
        
        Namespace namespace = basicSearchElementJDOM.getNamespace();
        Element fromElement = basicSearchElementJDOM.getChild
            (Literals.FROM, namespace);
        
        // FROM is mandatory
        if (fromElement == null)
            throw new BadQueryException (FROM_ELEMENT_MISSING);
        
        return new BasicQueryScope (fromElement);
    }
    
    /**
     * This IBasicExpressionCompilerProvider implementation returns a
     * BasicQueryCompiler instance in method getCompiler().
     *
     * @version $Revision: 1.21 $
     *
     **/
    public static class ExpressionCompilerProvider implements IBasicExpressionCompilerProvider {
        
        /**
         * Returns an IBasicExpressionCompiler for the given parameters.
         *
         * @param    query               the  IBasicQuery.
         * @param    propertyProvider    the  PropertyProvider to use (may be
         *                               <code>null</code>).
         *
         * @return     an IBasicExpressionCompiler for the given parameters.
         */
        public IBasicExpressionCompiler getCompiler(IBasicQuery query, PropertyProvider propertyProvider) throws BadQueryException {
            return new BasicExpressionCompiler(query, propertyProvider);
        }
    }
}

