/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/datastore/search/XQueryExecuter.java,v 1.4 2004/09/15 14:58:25 pnever Exp $
 * $Revision: 1.4 $
 * $Date: 2004/09/15 14:58:25 $
 *
 * ====================================================================
 *
 * 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 java.util.*;

import com.softwareag.common.instrumentation.logging.Level;
import com.softwareag.common.instrumentation.logging.Logger;
import com.softwareag.common.instrumentation.logging.LoggerFactory;
import com.softwareag.common.instrumentation.logging.LoggerUtil;
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.objectModel.TXMLObjectIterator;
import com.softwareag.tamino.db.api.response.TResponse;
import org.apache.slide.common.SlideRuntimeException;
import org.apache.slide.search.RequestedResource;
import org.apache.slide.search.SearchException;
import org.apache.slide.search.basic.BasicResultSetImpl;
import org.apache.slide.search.basic.IBasicQuery;
import org.apache.slide.search.basic.IBasicResultSet;
import org.apache.slide.security.AccessDeniedException;
import org.apache.slide.store.tamino.common.XDescriptorsHandler;
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.jdomobjects.XDescriptors;
import org.apache.slide.store.tamino.jdomobjects.XFactory;
import org.apache.slide.store.tamino.store.ISlideAccessor;
import org.apache.slide.store.tamino.store.XParentStore;
import org.apache.slide.structure.ObjectNode;
import org.apache.slide.util.ClassName;
import org.apache.slide.util.Configuration;
import org.apache.slide.util.XAssertionFailed;
import org.jaxen.JaxenException;
import org.jaxen.XPath;
import org.jaxen.jdom.JDOMXPath;
import org.jdom.Element;
import org.jdom.Text;

/**
 * An XQueryExecuter can execute the queryString on the xdav metadata
 *
 * @author <a href="mailto:martin.wallmer@softwareag.com">Martin Wallmer</a>
 * @version $Revision: 1.4 $
 */
public class XQueryExecuter implements IQueryExecuter {
    
    private static final String LOGNAME = LoggerUtil.getThisClassName();
    private static final String CLASSNAME = new ClassName(LOGNAME).getPlainName();
    private static Logger logger = LoggerFactory.getLogger(LOGNAME);
    
    private ISlideAccessor slideAccessor;
    private XBasicQueryImpl query;
    
    /**
     * Constructor
     *
     * @param slideAccessor the slideAccessor (store)
     */
    XQueryExecuter (ISlideAccessor slideAccessor, IBasicQuery query) {
        this.slideAccessor = slideAccessor;
        this.query = (XBasicQueryImpl) query;
    }
    
    /**
     * executes the taminoQuery on xdav:metadata.
     *
     * @param    taminoQuery         a  String
     *
     * @return   an IBasicResultSet
     *
     * @throws   SearchException
     *
     */
    public IBasicResultSet execute (String taminoQuery) throws SearchException {
        if( logger.isLoggable(Level.FINE) ){
            logger.entering( CLASSNAME, "execute", new Object[] {taminoQuery} );
        }
        
        IBasicResultSet resultSet = new BasicResultSetImpl();
        
        try {
            XConnectionKey conKey = new XConnectionKey (slideAccessor, "XQueryExecuter");
            XConnection con = XConnectionPool.getInstance().getXConnection (conKey);
            TXMLObjectAccessor objAccessor =
                con.newXMLJObjectAccessor (XGlobals.META_COLLECTION);
            
            // System.out.println(taminoQuery);
            TQuery tQuery = TQuery.newInstance (taminoQuery);
            
            TResponse response;
            
            if (query.isLimitDefined() && query.isMetaLimitAllowed ()) {
                
                // we use a hidden feature of API: if only the first bunch
                // of results is read from Tamino, local transaction mode is
                // possible
                response = objAccessor.query (tQuery, query.getLimit());
            }
            else {
                response = objAccessor.query (tQuery);
            }
            con.close();
            
            if (!response.getReturnValue().equals("0"))
                throw new SearchException ("query failed, return value is " + response.getReturnValue());
            
            TXMLObjectIterator it = response.getXMLObjectIterator();
            
            boolean useBinding = Configuration.useBinding(query.getStore());
            
            XDescriptorsHandler xdh = (XDescriptorsHandler)((XParentStore)slideAccessor).getDescriptorsStore().getDescriptorsHandler();
            
            ContentQueryFilter resultCache = new ContentQueryFilter(useBinding, it, query, xdh);
            Iterator nodeIterator = resultCache.getNodeIterator();
            
            XPathPropertyExecuter propExecuter =
                new XPathPropertyExecuter (query, resultCache, slideAccessor);
            
            propExecuter.resolveXPathProperties();
            
            while (nodeIterator.hasNext()) {
                
                ObjectNode node = (ObjectNode)nodeIterator.next();
                
                // ignore forbidden resources
                try {
                    RequestedResource resource = new XResourceImpl (node, query, resultCache);
                    resultSet.add  (resource);
                }
                catch (AccessDeniedException e) {}
            }
        }
        
        catch (Exception e) {
            e.printStackTrace();
            throw new SlideRuntimeException (e.getMessage(), true);
        }
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "execute", resultSet);
        return resultSet;
    }
}

/**
 * A ContentQueryFilter can be created, after the result set for a query is
 * known. It can be used to set a filter for XPath property queries.
 **/
class ContentQueryFilter implements IContentQueryFilter {
    private Map inoIdMap = new HashMap ();
    
    private List nodeList = new ArrayList (20);
    
    private XPath xPath;
    
    /**
     * Constructor
     *
     * @param    useBinding          indicate if binding is used
     * @param    resultIterator      iterates thru result set
     * @param    query               the original query
     *
     * @throws   TException
     *
     */
    ContentQueryFilter (boolean useBinding,
                        TXMLObjectIterator resultIterator,
                        XBasicQueryImpl query,
                        XDescriptorsHandler xdh)
        throws TException {
        ObjectNode node;
        
        LimitIterator it;
        if (query.isLimitDefined() && query.isMetaLimitAllowed())
            it = new LimitIterator (resultIterator, query.getLimit());
        else
            it = new LimitIterator (resultIterator, 0);
        
        XDescriptorsMappingList lst = query.getDescriptorsMappingList();
        try {
            String xPathString = ".//descriptor/propertyList/" +
                XGlobals.TAMINO_NAMESPACE_PREFIX + ":" +
                XGlobals.CONTENT_ID + "/text()";
            
            // System.out.println (xPathString);
            
            xPath = new JDOMXPath (xPathString);
            xPath.addNamespace (XGlobals.TAMINO_NAMESPACE_PREFIX, XGlobals.TAMINO_NAMESPACE_URI);
            
        } catch (JaxenException e) {
            throw new XAssertionFailed (e);
        }
        
        while (it.hasNext()) {
            TXMLObject descsObject = null;
            
            descsObject = it.next();
            
            Element el = (Element) descsObject.getElement();
            
            XContentId contentId = getContentId (el);
            if (contentId != null) { // can happen for empty content or directories
                
                String schema = contentId.getSchema();
                String inoId = contentId.getId();
                
                Set idList = (Set) inoIdMap.get (schema);
                if (idList == null) {
                    idList = new HashSet (20);
                    inoIdMap.put (schema, idList);
                }
                idList.add (inoId);
            }
            
            XDescriptors xDescs = XFactory.loadDescriptor(el);
            
            if (useBinding) {
                XDescriptorsMapping mapping;
                
                mapping = lst.lookup (xDescs.getUuri());
                if (mapping == null) {
                    throw new XAssertionFailed ("descriptor not found: " + xDescs.getUuri());
                }
                nodeList.addAll (mapping.createObjectNodes(xDescs));
                // ===============================================
                // TODO: call xhd.cacheQueryResults() somehow here
                // ===============================================
            }
            else {
                node = xDescs.getUuriObjectNode();
                node.setUri(node.getUuri());
                nodeList.add (node);
                xDescs = xdh.cacheQueryResult(node.getUri(), xDescs);
            }
        }
    }
    
    /**
     * Method getContentId
     *
     * @param    el                  an Element
     *
     * @return   a XContentId
     *
     */
    private XContentId getContentId (Element el) {
        XContentId contentId = null;
        
        try {
            Iterator it = xPath.selectNodes(el).iterator();
            
            if (it.hasNext()) {
                
                Text tContentId = (Text)it.next();
                contentId = new XContentId (tContentId.getText());
            }
            
        } catch (JaxenException e) {
            throw new XAssertionFailed (e);
        }
        
        return contentId;
    }
    
    /**
     * Method getNodeIterator
     *
     * @return   an Iterator
     *
     */
    Iterator getNodeIterator () {
        return nodeList.iterator();
    }
    
    /**
     * Method getXPathFilter
     *
     * @param    schema      the schema, for which the filter is to be returned
     *
     * @return   the filter in the form [@ino:id='1' or @ino:id='25' or ... @ino:id='4711']
     *
     */
    public String getXPathFilter (String schema) {
        StringBuffer sb = new StringBuffer();
        if (hasResults (schema)) {
            sb.append ("[");
            Iterator it = ((Set) inoIdMap.get (schema)).iterator();
            while (it.hasNext()) {
                String inoId = (String)it.next();
                sb.append ("@ino:id='" + inoId + "'");
                if (it.hasNext())
                    sb.append (" or ");
            }
            sb.append ("]");
        }
        
        return sb.toString();
    }
    
    /**
     * returns true, if the result set has entries for this schema
     *
     * @param    schema              a  String
     *
     * @return   a boolean
     *
     */
    public boolean hasResults (String schema) {
        Set inoIdSet = (Set) inoIdMap.get (schema);
        return inoIdSet != null;
    }
}





