/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/datastore/search/XPathContentQuery.java,v 1.3 2004/07/30 06:51:58 ozeigermann Exp $
 * $Revision: 1.3 $
 * $Date: 2004/07/30 06:51:58 $
 *
 * ====================================================================
 *
 * Copyright 1999-2004 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.store.tamino.datastore.search;

import com.softwareag.tamino.db.api.accessor.TQuery;
import com.softwareag.tamino.db.api.accessor.TXMLObjectAccessor;
import com.softwareag.tamino.db.api.common.TException;
import com.softwareag.tamino.db.api.objectModel.TXMLObject;
import com.softwareag.tamino.db.api.response.TResponse;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.slide.common.PropertyName;
import org.apache.slide.search.InvalidQueryException;
import org.apache.slide.search.SearchException;
import org.apache.slide.store.tamino.common.XGlobals;
import org.apache.slide.store.tamino.datastore.XConnection;
import org.apache.slide.store.tamino.datastore.XConnectionKey;
import org.apache.slide.store.tamino.datastore.XConnectionPool;
import org.apache.slide.store.tamino.datastore.XContentId;
import org.apache.slide.store.tamino.store.ISlideAccessor;
import org.jdom.Element;

/**
 * <p>Represents a content query. Once executed, it contains an IBasicResultSet
 * (all metadata objects, for which their content produces a result on the query)
 * If the result of this query is requested in <SELECT>, the content query result
 * is stored in a hashmap (key: contentId).</p>
 * <p>In special cases it is possible, to combine several ContentQueries within
 * Tamino query. In this case results may be added from outside, no extra
 * execution is necessary.
 * </p>
 *
 * @see XPathPropertyExecuter
 * @author <a href="mailto:martin.wallmer@softwareag.com">Martin Wallmer</a>
 * @version $Revision: 1.3 $
 */
public class XPathContentQuery {
    
    /** true, if the result of this query is requested in <SELECT> */
    private boolean isRequested = true;
    
    /** the result of the content query */
    private ContentResult contentResult = new ContentResult();
    
    
    //private Set contentIds;
    
    /** query only for members of result set */
    private IContentQueryFilter contentQueryFilter;
    
    /** the xpath expression for Tamino */
    private XPath xPath;
    
    /** this belongs to this query */
    private XBasicQueryImpl query;
    
    /** The accessor for the store (get tamino parameters) */
    private ISlideAccessor slideAccessor;
    
    /** true, if the query was executed */
    private boolean isResolved;
    
    /** the name (identifier) of this contentQuery */
    private PropertyName queryName;
    
    /**
     * constructs an XContentQuery
     *
     * @param sXPath the XPath expression
     * @param slideAccessor to get access to the store parameters
     * @param propName the name (identifier) of this contentQuery
     */
    public XPathContentQuery (String sXPath,
                              ISlideAccessor slideAccessor,
                              PropertyName propName,
                              XBasicQueryImpl query)
        throws InvalidQueryException  {
        this.xPath = new XPath (sXPath);
        this.slideAccessor = slideAccessor;
        this.queryName = propName;
        this.query = query;
        
        XOrderBy orderBy = (XOrderBy)query.getOrderBy();
        if (orderBy != null) {
            String sortBy = orderBy.getXPathSortByExpression();
            xPath.setSortBy (sortBy);
        }
    }
    
    /**
     * Method getXPath
     *
     * @return   the xPath expression for this query
     *
     */
    String getXPathString () {
        return xPath.getXpathString();
    }
    
    /**
     * If result set is already known, set a filter with contentIds
     *
     * @param    filter              an IContentQueryFilter
     *
     */
    void setFilter (IContentQueryFilter filter) {
        contentQueryFilter = filter;
    }
    
    /**
     * Executes the query. The result is cashed for the lifetime of this query.
     * Tries to limit the result if allowed (if this query is in context of
     * &lt;WHERE&gt;
     *
     * @throws   SearchException
     *
     */
    public void execute (int limit) throws SearchException {
        XContentId contentId = null;
        boolean doExecute = true;
        
        if (!query.isContentLimitAllowed())
            limit = 0;
//
        //        if (isRequested) {
        //            contentIds = contentResult.keySet();
        //        }
        //        else {
        //            contentIds = new HashSet ();
        //        }
        
        xPath.setContentQueryFilter (contentQueryFilter);
        
        if (contentQueryFilter != null) {
            doExecute = contentQueryFilter.hasResults (xPath.getSchema());
            xPath.setContentQueryFilter (contentQueryFilter);
        }
        
        String xPathWithFilter = xPath.getXpathString();
        
        // no need to execute if there is no result for this query
        if (doExecute) {
            String collection = slideAccessor.getParameter(XGlobals.TAMINO_COLLECTION);
            try {
                XConnectionKey conKey = new XConnectionKey (slideAccessor);
                XConnection con = XConnectionPool.getInstance().getXConnection (conKey);
                TXMLObjectAccessor objAccessor =
                    con.newXMLJObjectAccessor (collection);
                
                TQuery tQuery = TQuery.newInstance (xPathWithFilter);
                TXMLObject txmlObject = null;
                
                // System.out.println(xPathWithFilter);
                
                TResponse response;
                if (limit > 0)
                    response = objAccessor.query (tQuery, limit);
                    
                else
                    response = objAccessor.query (tQuery);
                
                if (!response.getReturnValue().equals("0"))
                    throw new InvalidQueryException
                        ("query failed, return value is " + response.getReturnValue());
                
                LimitIterator it = new LimitIterator
                    (response.getXMLObjectIterator(), limit);
                
                while (it.hasNext()) {
                    txmlObject = it.next();
                    
                    contentId = new XContentId (txmlObject.getId(),
                                                xPath.getSchema(),
                                                true);
                    
                    if (isRequested) {
                        addResultObject ((Element)txmlObject.getElement(), contentId);
                    }
                        
                    else {
                        addResultObject (null, contentId);
                        // contentIds.add (contentId);
                    }
                }
                
                setIsResolved();
                con.close();
            }
            catch (TException e) {
                throw new SearchException (e.getRootTCause().getMessage());
            }
            catch (Exception e) {
                throw new SearchException (e);
            }
        }
    }
    
    /**
     * indicate that no more execution is necessary
     *
     */
    void setIsResolved() {
        isResolved = true;
    }
    
    boolean isResolved () {
        return isResolved;
    }
    
    /**
     * Method addResultObject
     *
     * @param    e                   the result element to be added
     * @param    contentId           the contentId of the resource, to which element
     *                               shall be added
     *
     */
    void addResultObject (Element e, XContentId contentId) {
        contentResult.put (contentId, (Element)e.clone());
    }
    
    /**
     * Execute this query. Use no limit.
     *
     * @throws   SearchException
     *
     */
    public void execute () throws SearchException {
        execute (0);
    }
    
    /**
     * Accesses all contentIds (XContentId) for the documents of the
     * result set.
     *
     * @return   an Iterator over the contentIds of the result.
     *
     */
    Iterator contentIdIterator () {
        // return contentIds.iterator();
        return contentResult.keySet().iterator();
    }
    
    /**
     * Returns one document of the result set.
     *
     * @param    contId             for which resource the result is to be returned
     *
     * @return   the result element
     *
     * @throws   SearchException
     *
     */
    public Element getResult (XContentId contId) throws SearchException {
        if (!isResolved) {
            execute ();
        }
        
        return contentResult.get (contId);
    }
    
    /**
     * queryName accessor
     *
     * @return   the name of the query (as PropertyName)
     *
     */
    public PropertyName getQueryName () {
        return queryName;
    }
    
    /**
     * Method getXPath
     *
     * @return   a XPath
     *
     */
    public XPath getXPath () {
        return xPath;
    }
}

/**
 * a ContentResult is a map of elements, identified by the contentID.
 * Each content result element is a wrapper for several result elements
 * belonging to one resource (represented by content id)
 */
class ContentResult {
    
    private Map contentMap = new HashMap ();
    
    void put  (XContentId id, Element result) {
        Element wrapper = (Element)contentMap.get (id);
        if (wrapper == null) {
            wrapper = new Element (XGlobals.TMP_RESULT_WRAPPER);
            contentMap.put (id, wrapper);
        }
        
        wrapper.addContent (result);
    }
    
    Element get (XContentId id) {
        return (Element)contentMap.get (id);
    }
    
    Set keySet () {
        return contentMap.keySet();
    }
}
