/*
 * $Header: /home/cvspublic/jakarta-slide/src/share/org/apache/slide/search/basic/BasicQueryImpl.java,v 1.16 2004/07/28 09:35:02 ib Exp $
 * $Revision: 1.16 $
 * $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 java.util.List;

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.InvalidQueryException;
import org.apache.slide.search.InvalidScopeException;
import org.apache.slide.search.PropertyProvider;
import org.apache.slide.search.QueryScope;
import org.apache.slide.search.SearchException;
import org.apache.slide.search.SearchQueryResult;
import org.apache.slide.search.SearchToken;
import org.apache.slide.store.AbstractStore;
import org.jdom.Element;
import org.jdom.Namespace;

/**
 * BasicQueryImpl represents the generic (store independent) implementation of
 * BasicSearch.
 *
 * @version $Revision: 1.16 $
 */
public class BasicQueryImpl extends BasicQuery 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";

    /**
     * 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
     * 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
     * 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";

    /**
     * Message of a BadQueryException that is thrown if the query element
     * contains a &lt;limit&gt; element but no &lt;nresults&gt; element.
     */
    public static final String NRESULTS_MISSING = "Required element <nresults> (when limit is supplied) not supplied";

    /**
     * Message of a BadQueryException that is thrown if the &lt;nresults&gt; element
     * is not an integer.
     */
    public static final String NRESULTS_MUST_BE_AN_INTEGER = "<nresults> should be an integer";

    /** the NotNormalizer, may be overridden in extending classes */
    protected NotNormalizer notNormalizer;

//    IBasicExpressionFactory defaultExpressionFactory = new BasicExpressionFactory ();

    /**
     * Creates a BasicQueryImpl.
     */
    public BasicQueryImpl (SearchToken searchToken) {
        super.init (searchToken);
        notNormalizer = new NotNormalizer ();
    }

    /**
     * Default constructor, to enable creation by reflection
     */
    public BasicQueryImpl() {
        notNormalizer = new NotNormalizer ();
    }

    /**
     * Creates a BasicQueryImpl. Used for testing
     *
     * @param      expressionCompilerProvider  the provider which delivers the
     *                                         expression compiler to use.
     */
    public BasicQueryImpl(SearchToken searchToken, IBasicExpressionCompilerProvider expressionCompilerProvider) {
        init (searchToken);
        this.expressionCompilerProvider = expressionCompilerProvider;
    }

    /**
     * Executes a request.
     *
     * @return   a SearchQueryResult
     *
     * @throws   ServiceAccessException
     *
     */
    public SearchQueryResult execute () throws ServiceAccessException {
        SearchQueryResult result = null;

        try {

            IBasicResultSet resultSet = getExpression().execute();

            if (orderBy != null) {
                if (isLimitDefined()) {
                    result = new SearchQueryResult (resultSet,
                                                    orderBy.getComparator(),
                                                    limit);
                }
                else {
                    result = new SearchQueryResult (resultSet,
                                                    orderBy.getComparator());
                }

            }
            else {
                result = new SearchQueryResult (resultSet);
            }
            if (resultSet.isPartialResultSet()) {
                result.setStatus(SearchQueryResult.STATUS_PARTIAL_RESULT);
                result.setDescription ("The server truncated the result set");
            }
        }

        catch (InvalidScopeException e) {
            result = new SearchQueryResult ();
            result.setStatus (SearchQueryResult.STATUS_INVALID_SCOPE);
            result.setHref (queryScope.getHref());
        }

        catch (BadQueryException e) { // is this only INVALID_SCOPE?
            result = new SearchQueryResult ();
            result.setStatus (SearchQueryResult.STATUS_BAD_QUERY);
            result.setDescription (e.getMessage());
            result.setHref (queryScope.getHref());
        }

        catch (SearchException e) { // is this only INVALID_SCOPE?
            result = new SearchQueryResult ();
            result.setStatus (SearchQueryResult.STATUS_BAD_QUERY);
            result.setDescription (e.getMessage());
            result.setHref (queryScope.getHref());
        }

        return result;
    }

    /**
     * builds the internal structure from the JDOM tree
     *
     * @param    expressionElement  the (root) expression Element.
     * @param    propertyProvider   the PropertyProvider to use (may be
     *                              <code>null</code>).
     *
     * @throws   BadQueryException
     */
    public void parseQuery(Element expressionElement, PropertyProvider propertyProvider)
        throws BadQueryException {

        parseQueryWithoutExpression (expressionElement);
        IBasicExpressionCompiler expressionCompiler = expressionCompilerProvider.getCompiler(this, propertyProvider);

        // <where> is not mandatory
        if (whereElement != null) {
            List expressionList = whereElement.getChildren();
            if (expressionList.size() != 1) {
                throw new BadQueryException ("where must have exactly one nested element");
            }

            Element whereWithoutNot =
                notNormalizer.getQueryWithoutNotExpression((Element)expressionList.get (0));
            rootExpression = expressionCompiler.compile(whereWithoutNot);
        }
        else {
            rootExpression = expressionCompiler.compile(null);
        }
    }

    /**
     * Method getSelectedProperties
     *
     * @return   a SelectedPropertyList
     */
    public RequestedProperties requestedProperties () {
        return requestedProperties;
    }


    /**
     * 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 {
        this.parseQueryElement (basicSearchElement,
                                propertyProvider,
                                getScope(basicSearchElement));
    }

    /**
     * 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,
                                   QueryScope queryScope)

        throws BadQueryException {
        this.queryScope = queryScope;
        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);
    }



// TODO: move to BasicSearchLanguage
    /**
     * 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);
    }

    /**
     * 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);
        }

        requestedProperties = createRequestedProperties (propElement);

        //queryScope = new BasicQueryScope (fromElement);

        if (orderByElement != null) {
            orderBy = createNewOrderBy (orderByElement);
        }

        if (limitElement != null) {
            Element nResElem = limitElement.getChild (Literals.NRESULTS, namespace);
            if (nResElem == null)
                throw new BadQueryException (NRESULTS_MISSING);
            try {
                limit = new Integer (nResElem.getTextTrim()).intValue();
            }
            catch (NumberFormatException e) {
                throw new BadQueryException(NRESULTS_MUST_BE_AN_INTEGER);
            }
            limitDefined = true;
        }
    }

    protected OrderBy createNewOrderBy (Element orderByElement) throws InvalidQueryException {
        OrderBy result = new OrderBy ();
        result.init(orderByElement);
        return result;
    }




    /**
     * This method may be overridden, if a store specific implementation adds
     * new property semantic.
     *
     * @param    propElement         an Element
     *
     * @return   a RequestedProperties
     *
     */
    protected RequestedProperties createRequestedProperties (Element propElement) throws BadQueryException {
        try {
            return new RequestedPropertiesImpl (propElement);
        }
        catch (PropertyParseException e) {
            throw new BadQueryException (e.getMessage(), e);
        }
    }
}

