/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2003-2006, GeoTools Project Managment Committee (PMC)
 * 
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 */
package org.geotools.data.postgis;

import java.io.IOException;
import java.util.NoSuchElementException;
import java.util.logging.Logger;

import org.geotools.data.DataSourceException;
import org.geotools.data.DefaultQuery;
import org.geotools.data.FeatureLock;
import org.geotools.data.FeatureLockException;
import org.geotools.data.FeatureLocking;
import org.geotools.data.FeatureReader;
import org.geotools.data.LockingManager;
import org.geotools.data.Query;
import org.geotools.feature.Feature;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.FeatureType;
import org.geotools.feature.IllegalAttributeException;
import org.opengis.filter.Filter;


/**
 * Extends PostgisFeatureLocking with support for Locking.
 * 
 * <p>
 * This class will be, horror, modey. While the are plenty of Object Oriented
 * ways to fix this I have a deadline right now.
 * </p>
 * 
 * <p>
 * When the DataStore is constructed it will create a LockingManager only if
 * the Postgis implementation does not support database locking. If the
 * lockingManger is present it will be used.
 * </p>
 * 
 * <p>
 * If the lockingManger is not present, the this class will use Database
 * locking
 * </p>
 *
 * @author Jody Garnett, Refractions Research, Inc
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/plugin/postgis/src/main/java/org/geotools/data/postgis/PostgisFeatureLocking.java $
 * @version $Id: PostgisFeatureLocking.java 27862 2007-11-12 19:51:19Z desruisseaux $
 */
public class PostgisFeatureLocking extends PostgisFeatureStore
    implements FeatureLocking {
    /** The logger for the postgis module. */
    private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger(
            "org.geotools.data.postgis");
    FeatureLock featureLock = FeatureLock.TRANSACTION;

    public PostgisFeatureLocking(PostgisDataStore postgisDataStore,
        FeatureType featureType) throws IOException {
        super(postgisDataStore, featureType);
    }

    /**
     * Provide a FeatureLock for locking opperations to opperate against.
     * 
     * <p>
     * Initial Transactional duration locks can be restored with
     * setFeatureLock( FetaureLock.TRANSACTION )
     * </p>
     *
     * @param lock FeatureLock (or FeatureLock.TRANSACTION );
     *
     * @throws NullPointerException If lock was <code>null</code>
     *
     * @see org.geotools.data.FeatureLocking#setFeatureLock(org.geotools.data.FeatureLock)
     */
    public void setFeatureLock(FeatureLock lock) {
        if (lock == null) {
            throw new NullPointerException(
                "A FeatureLock is required - did you mean FeatureLock.TRANSACTION?");
        }

        featureLock = lock;
    }

    /**
     * Lock all Features
     *
     * @return Number of Locked features
     *
     * @throws IOException
     *
     * @see org.geotools.data.FeatureLocking#lockFeatures()
     */
    public int lockFeatures() throws IOException {
        return lockFeatures(Filter.INCLUDE);
    }

    /**
     * Lock features matching <code>filter</code>.
     *
     * @param filter
     *
     * @return Number of locked Features
     *
     * @throws IOException
     *
     * @see org.geotools.data.FeatureLocking#lockFeatures(org.geotools.filter.Filter)
     */
    public int lockFeatures(Filter filter) throws IOException {
        return lockFeatures(new DefaultQuery(getSchema().getTypeName(), filter));
    }
    public int lockFeatures( org.geotools.filter.Filter filter ) throws IOException {
        return lockFeatures(new DefaultQuery(getSchema().getTypeName(), filter));
    }
    /**
     * Lock features matching Query.
     * 
     * <p>
     * FeatureStores that have provided their own locking to will need to
     * override this method.
     * </p>
     *
     * @param query
     *
     * @return Number of locked Features
     *
     * @throws IOException If we could not determine which feature to lock
     *         based on Query
     * @throws UnsupportedOperationException When DataStore does not provide a
     *         LockingManager
     * @throws DataSourceException If feature to be locked does not exist
     *
     * @see org.geotools.data.FeatureLocking#lockFeatures(org.geotools.data.Query)
     */
    public int lockFeatures(Query query) throws IOException {
        LockingManager lockingManager = getDataStore().getLockingManager();

        if (lockingManager == null) {
            throw new UnsupportedOperationException(
                "DataStore not using lockingManager, must provide alternate implementation");
        }

        String typeName = getSchema().getTypeName();

        if ((query.getTypeName() != null)
                && !typeName.equals(query.getTypeName())) {
            throw new IOException("Query typeName does not match "
                + getSchema().getTypeName() + ":" + query);
        }

        // Reduce the Query to only return the FetureID here?
        // Good idea, but it's not working right now, so we're just using the query passed in.
        Query optimizedQuery = new DefaultQuery(typeName, query.getFilter(),
                query.getMaxFeatures(), Query.NO_NAMES, query.getHandle());
        FeatureIterator reader = getFeatures(query).features();
        Feature feature;
        int count = 0;
        LOGGER.info("got reader from query " + optimizedQuery
            + ", reader has next " + reader.hasNext());

        try {
            while (reader.hasNext()) {
                try {
                    feature = reader.next();
                    lockingManager.lockFeatureID(typeName, feature.getID(),
                        transaction, featureLock);
                    count++;
                    LOGGER.info("locked feature " + feature);
                } catch (FeatureLockException locked) {
                    LOGGER.info("feature lock exception");

                    // could not aquire - don't increment count                
                } catch (NoSuchElementException nosuch) {
                    throw new DataSourceException("Problem with "
                        + query.getHandle() + " while locking", nosuch);
                }
            }
        } finally {
            reader.close();
        }

        return count;
    }

    /**
     * HACK HACK HACK!!!  Don't use unless you're working on geoserver. just
     * using ints for return now, to easily swap out with what we've got going
     * on right now.
     *
     * @param feature DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     *
     * @throws IOException DOCUMENT ME!
     * @throws UnsupportedOperationException DOCUMENT ME!
     */
    public int lockFeature(Feature feature) throws IOException {
        LockingManager lockingManager = getDataStore().getLockingManager();

        if (lockingManager == null) {
            throw new UnsupportedOperationException(
                "DataStore not using lockingManager, must provide alternate implementation");
        }

        try {
            //TODO: more checking here, check feature.typename == this.typename
            //perhaps even feature.getFeatureType == this.featureType.
            lockingManager.lockFeatureID(tableName, feature.getID(),
                getTransaction(), featureLock);

            return 1;
        } catch (FeatureLockException locked) {
            return 0;
        }
    }

    /**
     * Unlock all Features.
     *
     * @throws IOException
     *
     * @see org.geotools.data.FeatureLocking#unLockFeatures()
     */
    public void unLockFeatures() throws IOException {
        unLockFeatures(Filter.INCLUDE);
    }

    /**
     * Unlock Features specified by <code>filter</code>.
     *
     * @param filter
     *
     * @throws IOException
     *
     * @see org.geotools.data.FeatureLocking#unLockFeatures(org.geotools.filter.Filter)
     */
    public void unLockFeatures(Filter filter) throws IOException {
        unLockFeatures(new DefaultQuery(getSchema().getTypeName(), filter));
    }

    /**
     * Unlock features specified by the <code>query</code>.
     * 
     * <p>
     * FeatureStores that have provided their own locking to will need to
     * override this method.
     * </p>
     *
     * @param query
     *
     * @throws IOException
     * @throws UnsupportedOperationException If lockingManager is not provided
     *         by DataStore subclass
     * @throws DataSourceException Filter describes an unlocked Feature, or
     *         authorization not held
     *
     * @see org.geotools.data.FeatureLocking#unLockFeatures(org.geotools.data.Query)
     */
    public void unLockFeatures(Query query) throws IOException {
        LockingManager lockingManager = getDataStore().getLockingManager();

        if (lockingManager == null) {
            throw new UnsupportedOperationException(
                "DataStore not using lockingManager, must provide alternate implementation");
        }

        // Could we reduce the Query to only return the FetureID here?
        //
        FeatureIterator reader = getFeatures(query).features();
        String typeName = getSchema().getTypeName();
        Feature feature;

        try {
            while (reader.hasNext()) {
                try {
                    feature = reader.next();
                    lockingManager.unLockFeatureID(typeName, feature.getID(),
                        getTransaction(), featureLock);
                } catch (NoSuchElementException nosuch) {
                    throw new DataSourceException("Problem with "
                        + query.getHandle() + " while locking", nosuch);
                }
            }
        } finally {
            reader.close();
        }
    }
}
