/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 com.hp.hpl.jena.tdb.transaction;

import org.apache.jena.atlas.lib.Sync ;

import com.hp.hpl.jena.query.Dataset ;
import com.hp.hpl.jena.query.DatasetFactory ;
import com.hp.hpl.jena.query.ReadWrite ;
import com.hp.hpl.jena.sparql.JenaTransactionException ;
import com.hp.hpl.jena.sparql.core.DatasetGraphTrackActive ;
import com.hp.hpl.jena.sparql.util.Context ;
import com.hp.hpl.jena.tdb.StoreConnection ;
import com.hp.hpl.jena.tdb.TDB ;
import com.hp.hpl.jena.tdb.base.file.Location ;
import com.hp.hpl.jena.tdb.store.DatasetGraphTDB ;
import com.hp.hpl.jena.update.GraphStore ;

/** Transactional DatasetGraph that allows one active transaction.
 * For multiple read transactions, create multiple DatasetGraphTransaction objects.
 * This is analogous to a "connection" in JDBC.
 */

public class DatasetGraphTransaction extends DatasetGraphTrackActive implements GraphStore, Sync
{
    /* Initially, the app can use this DatasetGraph non-transactionally.
     * But as soon as it starts a transaction, the dataset can only be used
     * inside transactions. 
     */
    
    
    // Two per-thread state variables:
    //   txn: ThreadLocalTxn -- the transactional , one time use dataset
    //   isInTransactionB: ThreadLocalBoolean -- flags true between begin and commit/abort, and end for read transactions.

    static class ThreadLocalTxn extends ThreadLocal<DatasetGraphTxn>
    {
        // This is the default - but nice to give it a name and to set it clearly.
        @Override protected DatasetGraphTxn initialValue() {
            return null ;
        }
    }

    static class ThreadLocalBoolean extends ThreadLocal<Boolean>
    {
        @Override protected Boolean initialValue() {
            return false ;
        }
    }

    // Transaction per thread.
    private ThreadLocalTxn txn = new ThreadLocalTxn() ;
    private ThreadLocalBoolean inTransaction = new ThreadLocalBoolean() ;
    
    private final StoreConnection sConn ;
    private boolean isClosed = false ; 

    public DatasetGraphTransaction(Location location)
    {
        sConn = StoreConnection.make(location) ;
    }

    public Location getLocation()       { return sConn.getLocation() ; }
    
    public DatasetGraphTDB getDatasetGraphToQuery()
    {
        checkNotClosed() ;
        return get() ;
    }
    
    public DatasetGraphTDB getBaseDatasetGraph()
    {
        checkNotClosed() ;
        return sConn.getBaseDataset() ;
    }

    @Override
    protected DatasetGraphTDB get()
    {
        if ( isInTransaction() )
        {
            DatasetGraphTxn dsgTxn = txn.get() ;
            if ( dsgTxn == null )
                throw new TDBTransactionException("In a transaction but no transactional DatasetGraph") ;
            return dsgTxn.getView() ;
        }
        
        if ( sConn.haveUsedInTransaction() )
            throw new TDBTransactionException("Not in a transaction") ;

        // Never used in a transaction - return underlying database for old style (non-transactional) usage.  
        return sConn.getBaseDataset() ;
    }

    @Override
    protected void checkActive()
    {
        checkNotClosed() ;
        if ( sConn.haveUsedInTransaction() && ! isInTransaction() )
            throw new JenaTransactionException("Not in a transaction ("+getLocation()+")") ;
    }

    @Override
    protected void checkNotActive()
    {
        checkNotClosed() ;
        if ( sConn.haveUsedInTransaction() && isInTransaction() )
            throw new JenaTransactionException("Currently in a transaction ("+getLocation()+")") ;
    }
    
    protected void checkNotClosed()
    {
        if ( isClosed )
            throw new JenaTransactionException("Already closed") ;
    }
    
    @Override
    public boolean isInTransaction()    
    { 
        checkNotClosed() ;
        return inTransaction.get() ;
    }

    public boolean isClosed()
    { return isClosed ; }
    
    public void syncIfNotTransactional()
    {
        if ( ! sConn.haveUsedInTransaction() )
            sConn.getBaseDataset().sync() ;
    }

    
    @Override
    protected void _begin(ReadWrite readWrite)
    {
        checkNotClosed() ;
        DatasetGraphTxn dsgTxn = sConn.begin(readWrite) ;
        txn.set(dsgTxn) ;
        inTransaction.set(true) ;
    }

    @Override
    protected void _commit()
    {
        checkNotClosed() ;
        txn.get().commit() ;
        inTransaction.set(false) ;
    }

    @Override
    protected void _abort()
    {
        checkNotClosed() ;
        txn.get().abort() ;
        inTransaction.set(false) ;
    }

    @Override
    protected void _end()
    {
        checkNotClosed() ;
        DatasetGraphTxn dsg = txn.get() ;
        // It's null if end() already called.
        if ( dsg  == null )
        {
            TDB.logInfo.warn("Transaction already ended") ;
            return ;
        }
        txn.get().end() ;
        // May already be false due to .commit/.abort.
        inTransaction.set(false) ;
        txn.set(null) ;
    }

    @Override
    public String toString()
    {
        try {
            // Risky ... 
            return get().toString() ;
            // Hence ...
        } catch (Throwable th) { return "DatasetGraphTransactional" ; }
    }
    
    @Override
    protected void _close()
    {
        if ( isClosed )
            return ;
        
        if ( ! sConn.haveUsedInTransaction() && get() != null )
        {
            // Non-transactional behaviour.
            DatasetGraphTDB dsg = get() ;
            dsg.sync() ;
            dsg.close() ;
            StoreConnection.release(dsg.getLocation()) ;
            isClosed = true ;
            return ;
        }
        
        if ( isInTransaction() )
        {
            TDB.logInfo.warn("Attempt to close a DatasetGraphTransaction while a transaction is active - ignored close ("+getLocation()+")") ;
            return ; 
        }
        isClosed = true ;
        txn.remove() ;
        inTransaction.remove() ;
    }

    @Override
    public Dataset toDataset()
    {
        return DatasetFactory.create(getDatasetGraphToQuery()) ;
    }
    
    @Override
    public Context getContext()
    {
        // Not the transactional dataset.
        return getBaseDatasetGraph().getContext() ;
    }

    @Override
    public void startRequest()
    {}

    @Override
    public void finishRequest()
    {}

    @Override
    public void sync()
    {
        if ( ! sConn.haveUsedInTransaction() && get() != null )
            get().sync() ;
    }
}

@@ -1876,36 +1876,35 @@
 ons.
- 
 %0A     *
-/%0A   
  %0A    
-%0A    // T
+ * There are t
 wo p
@@ -1934,18 +1934,19 @@
 es:%0A    
-//
+ * 
    txn: 
@@ -2008,18 +2008,19 @@
 set%0A    
-//
+ * 
    isInT
@@ -2127,16 +2127,24 @@
 actions.
+%0A     */
 %0A%0A    st

Reformat comment