import static java.util.concurrent.TimeUnit.MILLISECONDS; 

import java.lang.management.ManagementFactory; 
import java.lang.management.ThreadInfo; 
import java.lang.management.ThreadMXBean; 
import java.security.AccessControlContext; 
import java.security.AccessController; 
import java.security.Principal; 
import java.security.PrivilegedActionException; 
import java.security.PrivilegedExceptionAction; 
import java.security.ProtectionDomain; 
import java.util.Arrays; 
import java.util.Collections; 
import java.util.Map; 
import java.util.Set; 
import java.util.TreeMap; 
import java.util.concurrent.CyclicBarrier; 

import org.apache.commons.collections.set.SynchronizedSet; 

import javax.security.auth.Subject; 
import javax.security.auth.SubjectDomainCombiner; 

/** 
 * Try to test a possible deadlock in 
 * {@link SubjectDomainCombiner#combine(java.security.ProtectionDomain[], java.security.ProtectionDomain[])} between a 
 * internal monitor on the cached domains and a implicit monitor by use 
 * {@link SynchronizedSet#equals(java.lang.Object)} ({@link Subject#getPrincipals()}). 
 * 
 */ 
public class JI9043531 {
	 private static final long BARRIER_TIMEOUT_MS = 250; 

	  private static final Subject SUBJECT = new Subject(); 

	  public static void main(String[] args) throws Throwable { 
	    final CyclicBarrier barrier = new CyclicBarrier(3); 
	    // '3': 2 created threads and the current thread (test execution) 
	    final Map<String, Throwable> uncaughtExceptionMap = Collections.synchronizedMap( 
	            new TreeMap<String, Throwable>()); 

	    final BarrierSubjectDomainCombiner barrierSubjectDomainCombiner = new BarrierSubjectDomainCombiner(barrier, 
	                                                                                                       SUBJECT); 

	    final Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() { 
	      @Override 
	      public void uncaughtException(final Thread t, final Throwable e) { 
	        uncaughtExceptionMap.put(t.getName(), e); 
	      } 
	    }; 
	    try { 

	      AccessController.doPrivileged(deadlockAction(barrier, uncaughtExceptionHandler, barrierSubjectDomainCombiner), 
	                                    new AccessControlContext(AccessController.getContext(), barrierSubjectDomainCombiner)); 
	    } catch (PrivilegedActionException pae) { 
	      throw pae.getException(); 
	    } 
	    if (!uncaughtExceptionMap.isEmpty()) { 
	      throw uncaughtExceptionMap.values().iterator().next(); 
	    } 

	    final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); 

	    final long[] deadlocks = threadMXBean.findDeadlockedThreads(); 
	    if (deadlocks != null) { 
	      final ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(deadlocks, true, true); 
	      System.err.println(Arrays.deepToString(threadInfos)); 
	      System.exit(1); 
	    } 
	    System.exit(0); 
	  } 

	  private static PrivilegedExceptionAction<Void> deadlockAction( 
	          final CyclicBarrier barrier, 
	          final Thread.UncaughtExceptionHandler uncaughtExceptionHandler, 
	          final BarrierSubjectDomainCombiner barrierSubjectDomainCombiner) { 
	    return new PrivilegedExceptionAction<Void>() { 
	      @Override 
	      public Void run() throws Exception { 
	        final Set<Principal> principalSet = SUBJECT.getPrincipals(); 
	        final Principal userSubjectIdPrincipal = new Principal() { 
	          @Override 
	          public String getName() { 
	            return "userSubjectId"; 
	          } 
	        }; 

	        final Thread accThread = new Thread("AccessController.getContext()") { 
	          @Override 
	          public void run() { 
	            try { 
	              barrierSubjectDomainCombiner.armBarrier(); // arm barrier after thread creation 
	              AccessController.getContext(); 
	            } catch (Exception ex) { 
	              throw new RuntimeException(ex.getMessage(), ex); 
	            } 
	          } 
	        }; 
	        accThread.setUncaughtExceptionHandler(uncaughtExceptionHandler); 

	        final Thread subjectThread = new Thread("subject.getPrincipals().add()") { 
	          @Override 
	          public void run() { 
	            try { 
	              barrierSubjectDomainCombiner.armBarrier(); // arm barrier after thread creation 
	              principalSet.add(userSubjectIdPrincipal); 
	            } catch (Exception ex) { 
	              throw new RuntimeException(ex.getMessage(), ex); 
	            } 
	          } 
	        }; 
	        subjectThread.setUncaughtExceptionHandler(uncaughtExceptionHandler); 

	        accThread.start(); 
	        subjectThread.start(); 

	        barrier.await(BARRIER_TIMEOUT_MS, MILLISECONDS); 
	        return null; 
	      } 
	    }; 

	  } 

	  /** 
	   * This class drills the test code down just before the interesting method. 
	   * <p> 
	   * The passed {@code barrier} holds the method execution and waits until all threads are at this point. The risk to 
	   * fall down into the deadlock is very hight. 
	   * </p> 
	   * <p> 
	   * It's required to deactivate the {@code barrier} while the thread creation. Within the thread constructor this 
	   * method will also called but at this point this call isn't interesting for the specific failure. 
	   * <br>The barrier will armed later after the thread construction.</br> 
	   * </p> 
	   */ 
	  private static final class BarrierSubjectDomainCombiner extends SubjectDomainCombiner { 

	    private final CyclicBarrier barrier; 
	    private boolean armedBarrier = false; 

	    public BarrierSubjectDomainCombiner(final CyclicBarrier barrier, final Subject subject) { 
	      super(subject); 
	      this.barrier = barrier; 
	    } 

	    void armBarrier() { 
	      this.armedBarrier = true; 
	    } 

	    @Override 
	    public ProtectionDomain[] combine(ProtectionDomain[] currentDomains, ProtectionDomain[] assignedDomains) { 
	      if (armedBarrier) { 
	        try { 
	          this.barrier.await(BARRIER_TIMEOUT_MS, MILLISECONDS); 
	        } catch (Exception ex) { 
	          throw new RuntimeException(ex.getMessage(), ex); 
	        } 
	      } 
	      return super.combine(currentDomains, assignedDomains); //To change body of generated methods, choose Tools | Templates. 
	    } 

	  } 
}
