/**
 * 
 */
package com.thinkbiganalytics.metadata.modeshape;

import com.thinkbiganalytics.metadata.api.Command;
import com.thinkbiganalytics.metadata.api.MetadataAccess;
import com.thinkbiganalytics.metadata.modeshape.support.JcrUtil;
import com.thinkbiganalytics.metadata.modeshape.support.JcrVersionUtil;

import org.modeshape.jcr.api.txn.TransactionManagerLookup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import javax.inject.Inject;
import javax.inject.Named;
import javax.jcr.Node;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.transaction.NotSupportedException;
import javax.transaction.SystemException;
import javax.transaction.TransactionManager;


/**
 *
 * @author Sean Felten
 */
public class JcrMetadataAccess implements MetadataAccess {
    
    private static final Logger log = LoggerFactory.getLogger(JcrMetadataAccess.class);
    
    public static final String TBA_PREFIX = "tba";
    
    private static final ThreadLocal<Session> activeSession = new ThreadLocal<Session>() {
        protected Session initialValue() {
            return null;
        }
    };

    private static final ThreadLocal<Set<Node>> checkedOutNodes = new ThreadLocal<Set<Node>>() {

    };

    @Inject
    @Named("metadataJcrRepository")
    private Repository repository;
    
    @Inject
    private TransactionManagerLookup txnLookup;

    public static Session getActiveSession() {
        return activeSession.get();
    }


    /**
     * Return all nodes that have been checked out
     */
    public static Set<Node> getCheckedoutNodes() {
        return checkedOutNodes.get();
    }


    /**
     * Check out the node and add it to the Set of checked out nodes
     *
     *
     */
    public static void ensureCheckoutNode(Node n) throws RepositoryException {
        if (JcrUtil.isVersionable(n) && (!n.isCheckedOut() || (n.isNew() && !checkedOutNodes.get().contains(n)))) {
            JcrVersionUtil.checkout(n);
            checkedOutNodes.get().add(n);
        }
    }

    /**
     * A set of Nodes that have been Checked Out. Nodes that have the mix:versionable need to be Checked Out and then Checked In when updating.
     *
     * @see com.thinkbiganalytics.metadata.modeshape.support.JcrPropertyUtil.setProperty() which checks out the node before applying the update
     */
    public static void checkinNodes() throws RepositoryException {
        Set<Node> checkedOutNodes = getCheckedoutNodes();
        if (checkedOutNodes != null && !checkedOutNodes.isEmpty()) {
            for (Iterator<Node> itr = checkedOutNodes.iterator(); itr.hasNext(); ) {
                Node element = itr.next();
                JcrVersionUtil.checkin(element);
                itr.remove();
            }
        }
    }
    
    /* (non-Javadoc)
     * @see com.thinkbiganalytics.metadata.api.MetadataAccess#commit(com.thinkbiganalytics.metadata.api.Command)
     */
    @Override
    public <R> R commit(Command<R> cmd) {
        Session session = activeSession.get();
        Set<Node> checkedOutNodes = getCheckedoutNodes();
        
        if (session == null) {
            try {

                if (checkedOutNodes == null) {
                    this.checkedOutNodes.set(new HashSet<>());
                }
                activeSession.set(this.repository.login());

                TransactionManager txnMgr = this.txnLookup.getTransactionManager();
                
                try {
                    txnMgr.begin();
                    
                    R result = cmd.execute();
                    
                    activeSession.get().save();

                    checkinNodes();

                    txnMgr.commit();
                    return result;
                } catch (Exception e) {
                    log.warn("Exception while execution a transactional operation - rollng back", e);
                    
                    try {
                        txnMgr.rollback();
                    } catch (SystemException se) {
                        log.error("Failed to rollback tranaction as a result of other transactional errors", se);
                    }
                    
                    activeSession.get().refresh(false);
                    // TODO Use a better exception
                    throw new RuntimeException(e);
                } finally {
                    activeSession.get().logout();
                    activeSession.remove();
                    if(this.checkedOutNodes.get() != null) {
                        this.checkedOutNodes.get().clear();
                    }
                    this.checkedOutNodes.remove();
                }
            } catch (RuntimeException e) {
                throw e;
            } catch (RepositoryException e) {
                // TODO Use a better exception
                throw new RuntimeException(e);
            }
        } else {
            return cmd.execute();
        }
    }

    /* (non-Javadoc)
     * @see com.thinkbiganalytics.metadata.api.MetadataAccess#read(com.thinkbiganalytics.metadata.api.Command)
     */
    @Override
    public <R> R read(Command<R> cmd) {
        Session session = activeSession.get();
        
        if (session == null) {
            try {
                activeSession.set(this.repository.login());
                
                TransactionManager txnMgr = this.txnLookup.getTransactionManager();
                
                try {
                    txnMgr.begin();
                    
                    return cmd.execute();
                } catch (SystemException | NotSupportedException e) {
                    // TODO Use a better exception
                    throw new RuntimeException(e);
                } finally {
                    try {
                        txnMgr.rollback();
                    } catch (SystemException e) {
                        log.error("Failed to rollback transaction", e);
                    }
                    
                    activeSession.get().refresh(false);
                    activeSession.get().logout();
                    activeSession.remove();
                }
            } catch (RuntimeException e) {
                throw e;
            } catch (RepositoryException e) {
                // TODO Use a better exception
                throw new RuntimeException(e);
            }
        } else {
            return cmd.execute();
        }
    }
}

@@ -1325,17 +1325,118 @@
 e%3E%3E() %7B%0A
+        protected java.util.Set%3CNode%3E initialValue() %7B%0A            return new HashSet%3C%3E();%0A        %7D;
 %0A
-
     %7D;%0A%0A
@@ -2672,81 +2672,8 @@
 ();%0A
-        if (checkedOutNodes != null && !checkedOutNodes.isEmpty()) %7B%0A    
     
@@ -2761,20 +2761,16 @@
         
-  
-  
 Node ele
@@ -2784,28 +2784,24 @@
 itr.next();%0A
-    
             
@@ -2845,20 +2845,16 @@
         
-    
 itr.remo
@@ -2859,30 +2859,16 @@
 move();%0A
-            %7D%0A
         
@@ -3129,66 +3129,8 @@
 ();%0A
-        Set%3CNode%3E checkedOutNodes = getCheckedoutNodes();%0A
     
@@ -3188,136 +3188,8 @@
  %7B%0A%0A
-                if (checkedOutNodes == null) %7B%0A                    this.checkedOutNodes.set(new HashSet%3C%3E());%0A                %7D%0A
     
@@ -4362,32 +4362,32 @@
 ssion.remove();%0A
+
                 
@@ -4394,156 +4394,8 @@
     
-if(this.checkedOutNodes.get() != null) %7B%0A                        this.checkedOutNodes.get().clear();%0A                    %7D%0A                    this.
 chec

Use preferred method of ThreadLocal initialization and clearing