/*
 * $Header: /home/cvspublic/jakarta-slide/src/stores/org/apache/slide/store/impl/rdbms/RDBMSComparableResourcesPool.java,v 1.16 2005/01/10 18:18:57 luetzkendorf Exp $
 * $Revision: 1.16 $
 * $Date: 2005/01/10 18:18:57 $
 *
 * ====================================================================
 *
 * 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.impl.rdbms;

import java.lang.reflect.Constructor;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.slide.common.PropertyName;
import org.apache.slide.common.RequestedProperties;
import org.apache.slide.common.RequestedProperty;
import org.apache.slide.common.RequestedPropertyImpl;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.SlideException;
import org.apache.slide.common.SlideRuntimeException;
import org.apache.slide.content.NodeProperty;
import org.apache.slide.search.BadQueryException;
import org.apache.slide.search.PropertyProvider;
import org.apache.slide.search.QueryScope;
import org.apache.slide.search.SearchQuery;
import org.apache.slide.search.SearchToken;
import org.apache.slide.search.basic.ComparableResourceImpl;
import org.apache.slide.search.basic.ComparableResourcesPool;
import org.apache.slide.search.basic.IBasicQuery;
import org.apache.slide.security.AccessDeniedException;
import org.apache.slide.store.impl.rdbms.expression.RDBMSExpressionFactory;
import org.apache.slide.store.impl.rdbms.expression.RDBMSQueryContext;
import org.apache.slide.structure.ObjectNode;
import org.apache.slide.util.logger.Logger;

/**
 */
public class RDBMSComparableResourcesPool implements ComparableResourcesPool {

    private final AbstractRDBMSStore _store;
    private final RDBMSQueryContext _context;
    private final IBasicQuery _query;
    private final SearchToken _token;
    private final QueryScope _scope;
    private final Map _selectProperties;
    private final PropertyProvider _provider;

    private Set _pool;

    public RDBMSComparableResourcesPool(AbstractRDBMSStore store, 
                                        RDBMSQueryContext context,
                                        IBasicQuery query) {
        _store = store;
        _context = context;
        _query = query;
        _token = _query.getSearchToken();
        _scope = _query.getScope();
        _selectProperties = new HashMap();
        _provider = new RDBMSPropertyProvider(_query.getPropertyProvider(), _selectProperties);

        if (_query instanceof SearchQuery) {
            final RequestedProperties props = ((SearchQuery) _query).requestedProperties();
            if (!props.isAllProp()) {
                final Iterator iter = props.getRequestedProperties();
                while (iter.hasNext()) {
                    final RequestedProperty property = (RequestedProperty) iter.next();
                    final String selectKey = property.getNamespace() + property.getName();
                    if (_context.selects().containsKey(selectKey)) {
                        _selectProperties.put(property, new HashMap());
                    }
                }
            }
        }
    }

    public Iterator resourceIterator() {
        try {
            return getPool().iterator();
        }
        catch (BadQueryException e) {
            throw new SlideRuntimeException(e.toString());
        }
    }

    public Set getPool() throws BadQueryException {
        if (_pool == null) {
            try {
                ObjectNode[] objects = retrieveObjects();
                _pool = new HashSet(objects.length);
                for (int i = 0; i < objects.length; i++) {
                    try {
                        _pool.add(new ComparableResourceImpl(objects[i], _token, _scope, _provider));
                    }
                    catch (AccessDeniedException e) {
                        // ignore: object is not visible
                    }
                }
            } catch (ServiceAccessException e) {
                e.printStackTrace();
                throw new BadQueryException(e);
            } catch (SlideException e) {
                throw new BadQueryException(e);
            }
        }
        return _pool;
    }

    public boolean partialResult() {
        return false;
    }

    public QueryScope getScope() {
        return _scope;
    }

    private ObjectNode[] retrieveObjects() throws ServiceAccessException, BadQueryException {
        if (_store.getCurrentlyActiveTransactionalResource() == null) {
            Connection connection = null;
            try {
                connection = _store.getNewConnection();
                return retrieveObjects(connection);
            } catch (SQLException e) {
                throw new ServiceAccessException(_store, e);
            } finally {
                if (connection != null) {
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        _store.getLogger().log(e, AbstractRDBMSStore.LOG_CHANNEL, Logger.WARNING);
                    }
                }
            }
        } else {
            return retrieveObjects(_store.getCurrentConnection());
        }
    }
    
    private ObjectNode[] retrieveObjects(Connection connection) throws ServiceAccessException, BadQueryException {
        PreparedStatement statement = null;
        ResultSet result = null;
        ArrayList classNames = new ArrayList();
        ArrayList uris = new ArrayList();
        try {
            final String sql = compileSQL();
            if (_store.getLogger().isEnabled(Logger.INFO)) {
                _store.getLogger().log("executing: " + sql, AbstractRDBMSStore.LOG_CHANNEL, Logger.INFO);
            }
            statement = connection.prepareStatement(sql);
            result = statement.executeQuery();
            while (result.next()) {
                final String uri = result.getString(1);
                final String className = result.getString(2);
                uris.add(uri);
                classNames.add(className);
                Iterator iter = _selectProperties.keySet().iterator();
                while (iter.hasNext()) {
                    final RequestedProperty requested = (RequestedProperty) iter.next();
                    final String name = requested.getName();
                    final String namespace = requested.getNamespace();
                    final String alias = RDBMSExpressionFactory.propertyToAlias(requested.getName());
                    final String value = result.getString(alias);
                    final NodeProperty property = new NodeProperty(name, value, namespace);
                    final Map properties = (Map) _selectProperties.get(requested);
                    properties.put(uri, property);
                }
            }
        }
        catch (SQLException e) {
            throw new ServiceAccessException(_store, e);
        }
        finally {
            if (result != null) {
                try {
                    result.close();
                }
                catch (SQLException e) {
                    _store.getLogger().log(e, AbstractRDBMSStore.LOG_CHANNEL, Logger.WARNING);
                }
            }
            if (statement != null) {
                try {
                    statement.close();
                }
                catch (SQLException e) {
                    _store.getLogger().log(e, AbstractRDBMSStore.LOG_CHANNEL, Logger.WARNING);
                }
            }
        }
        int size = uris.size();
        ObjectNode[] objects = new ObjectNode[size];
        for (int i = 0; i < size; i++) {
            try {
                Class objclass = Class.forName((String) classNames.get(i));
                Class argClasses[] = { String.class };
                Object arguments[] = { uris.get(i) };
                Constructor constructor = objclass.getConstructor(argClasses);
                objects[i] = (ObjectNode) constructor.newInstance(arguments);
                objects[i].setUri(objects[i].getUuri());
            } catch (Exception e) {
                throw new ServiceAccessException(_store, e);
            }
        }
        return objects;
    }
    
    private String compileSQL() throws BadQueryException {
        String uri = _token.getSlideContext().getSlidePath(_scope.getHref());
        if (uri.endsWith("/")) {
            uri = uri.substring(0, uri.length()-1);
        }
        String query = "select " + compileSelect() +
                       " from " + compileJoins() + 
                       " where " + compileScope(uri);
        final String criteria = compileCriteria();
        query = (criteria != null && criteria.length() > 0) ? query + " AND " + criteria : query;
        return query;
    }
    
    private String compileSelect() {
        String select = "u.URI_STRING, o.CLASS_NAME";
        final Iterator iter = _selectProperties.keySet().iterator();
        while (iter.hasNext()) {
            final RequestedProperty property = (RequestedProperty) iter.next();
            final String selectKey = property.getNamespace() + property.getName();
            String propSelect = (String) _context.selects().get(selectKey);
            if (propSelect != null) {
                select += ", " + propSelect;
            }
        }
        return select;
    }
    
    private String compileScope(String uri) {
        switch (_scope.getDepth()) {
            case QueryScope.DEPTH_0: {
                return " u.URI_STRING = '" + uri + "'";
            }
            case QueryScope.DEPTH_1: {
                return " (u.URI_STRING = '" + uri + "'" +
                       " OR (u.URI_STRING LIKE '" + uri + "/%'" +
                            " AND u.URI_STRING NOT LIKE '" + uri + "/%/%'))";
            }
            case QueryScope.DEPTH_INFINITY:
            default: {
                return " (u.URI_STRING = '" + uri + "'" + 
                       " OR u.URI_STRING LIKE '" + uri + "/%')";
            }
        }
    }
    
    private String compileCriteria() {
        String result = null;
        if (_context.criteria().size() > 0) {
            result = "";
            Iterator iter = _context.criteria().iterator();
            while (iter.hasNext()) {
                result += iter.next();
            }
        }
        return result;
    }
    
    private String compileJoins() {
        String joins = "((OBJECT o " +
                      "inner join URI u on u.URI_ID = o.URI_ID) " +
                      "inner join VERSION_HISTORY vh on vh.URI_ID = u.URI_ID) ";
        Iterator iter = _context.joins().iterator();
        while (iter.hasNext()) {
            joins = "(" + joins + " " + iter.next() + ") ";
        }
        return joins;
    }

    private static class RDBMSPropertyProvider implements PropertyProvider {

        private final PropertyProvider _propertyProvider;
        private final Map _selectProperties;

        private RDBMSPropertyProvider(PropertyProvider propertyProvider, Map selectProperties) {
            _propertyProvider = propertyProvider;
            _selectProperties = selectProperties;
        }
        
        public boolean isSupportedProperty(String resourceUri, String propertyName, String propertyNamespace) throws SlideException {
            if (_selectProperties.containsKey(new RequestedPropertyImpl(propertyName, propertyNamespace))) {
                return true;
            }
            else if (_propertyProvider != null) {
                return _propertyProvider.isSupportedProperty(resourceUri, propertyName, propertyNamespace);
            }
            return false;
        }
        
        public boolean isSupportedProperty(String resourceUri, PropertyName propertyName) throws SlideException {
            return isSupportedProperty(resourceUri, propertyName.getName(), propertyName.getNamespace());
        }

        public Iterator getSupportedPropertiesNames(String resourceUri) throws SlideException {
            Iterator selected = _selectProperties.keySet().iterator();
            Iterator provided = null;
            if (_propertyProvider != null) {
                provided = _propertyProvider.getSupportedPropertiesNames(resourceUri);
            }
            return new PropertyNamesIterator(selected, provided);
        }

        public NodeProperty getProperty(String resourceUri, String propertyName, String propertyNamespace) throws SlideException {
            Map properties = (Map) _selectProperties.get(new RequestedPropertyImpl(propertyName, propertyNamespace));
            if (properties != null) {
                return (NodeProperty) properties.get(resourceUri);
            }
            else if (_propertyProvider != null) {
                return _propertyProvider.getProperty(resourceUri, propertyName, propertyNamespace);
            }
            return null;
        }
        
        public NodeProperty getProperty(String resourceUri, PropertyName propertyName) throws SlideException {
            return getProperty(resourceUri, propertyName.getName(), propertyName.getNamespace());
        }

        public Iterator getSupportedProperties(String resourceUri) throws SlideException {
            Iterator selected = _selectProperties.values().iterator();
            Iterator provided = null;
            if (_propertyProvider != null) {
                provided = _propertyProvider.getSupportedProperties(resourceUri);
            }
            return new NodePropertiesIterator(resourceUri, selected, provided);
        }

        private static class PropertyNamesIterator implements Iterator {

            private final Iterator _selectedProperties;
            private final Iterator _providedProperties;
            
            private PropertyNamesIterator(Iterator selectedProperties, Iterator providedProperties) {
                _selectedProperties = selectedProperties;
                if (providedProperties != null) {
                    _providedProperties = providedProperties;
                }
                else {
                    _providedProperties = Collections.EMPTY_LIST.iterator();
                }
            }
            
            public void remove() {
                throw new UnsupportedOperationException();
            }

            public boolean hasNext() {
                return _selectedProperties.hasNext() || _providedProperties.hasNext();
            }

            public Object next() {
                if (_selectedProperties.hasNext()) {
                    RequestedProperty property = (RequestedProperty) _selectedProperties.next();
                    return PropertyName.getPropertyName(property.getName(), property.getNamespace());
                }
                return _providedProperties.next();
            }
            
        }
        
        private static class NodePropertiesIterator implements Iterator {

            private String _resourceUri;
            private Iterator _selectedProperties;
            private Iterator _providedProperties;

            private NodePropertiesIterator(String resourceUri, Iterator selectedProperties, Iterator providedProperties) {
                _resourceUri = resourceUri;
                _selectedProperties = selectedProperties;
                if (providedProperties != null) {
                    _providedProperties = providedProperties;
                }
                else {
                    _providedProperties = Collections.EMPTY_LIST.iterator();
                }
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

            public boolean hasNext() {
                return _selectedProperties.hasNext() || _providedProperties.hasNext();
            }

            public Object next() {
                if (_selectedProperties.hasNext()) {
                    Map properties = (Map) _selectedProperties.next();
                    return (NodeProperty) properties.get(_resourceUri);
                }
                return _providedProperties.next();
            }

        }

    }
    
}
