001/** 002 * Copyright 2010-2013 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.common.util.execute.impl; 017 018import static com.google.common.base.Preconditions.checkNotNull; 019 020import java.lang.Thread.UncaughtExceptionHandler; 021import java.util.ArrayList; 022import java.util.List; 023 024import org.kuali.common.util.FormatUtils; 025import org.kuali.common.util.base.Threads; 026import org.kuali.common.util.execute.Executable; 027import org.kuali.common.util.log.LoggerUtils; 028import org.slf4j.Logger; 029 030import com.google.common.base.Optional; 031import com.google.common.base.Stopwatch; 032import com.google.common.collect.ImmutableList; 033 034/** 035 * Create a new thread for each executable in the list and run them all concurrently. 036 */ 037public final class ConcurrentExecutables implements Executable, UncaughtExceptionHandler { 038 039 private static final Logger logger = LoggerUtils.make(); 040 041 private final ImmutableList<Executable> executables; 042 private final boolean skip; 043 private final boolean timed; 044 045 // If any thread throws an exception, this gets filled in 046 private Optional<IllegalStateException> uncaughtException = Optional.absent(); 047 048 public static void execute(Executable... executables) { 049 create(executables).execute(); 050 } 051 052 public static void execute(List<Executable> executables) { 053 create(executables).execute(); 054 } 055 056 public static ConcurrentExecutables create(Executable... executables) { 057 return builder(executables).build(); 058 } 059 060 public static ConcurrentExecutables create(List<Executable> executables) { 061 return builder(executables).build(); 062 } 063 064 public static Builder builder(Executable... executables) { 065 return new Builder(executables); 066 } 067 068 public static Builder builder(List<Executable> executables) { 069 return new Builder(executables); 070 } 071 072 public static class Builder implements org.apache.commons.lang3.builder.Builder<ConcurrentExecutables> { 073 074 // Required 075 private final List<Executable> executables; 076 077 // Optional 078 private boolean skip = false; 079 private boolean timed = false; 080 081 public Builder(Executable... executables) { 082 this(ImmutableList.copyOf(executables)); 083 } 084 085 public Builder(List<Executable> executables) { 086 this.executables = ImmutableList.copyOf(executables); 087 } 088 089 public Builder timed(boolean timed) { 090 this.timed = timed; 091 return this; 092 } 093 094 public Builder skip(boolean skip) { 095 this.skip = skip; 096 return this; 097 } 098 099 @Override 100 public ConcurrentExecutables build() { 101 ConcurrentExecutables instance = new ConcurrentExecutables(this); 102 validate(instance); 103 return instance; 104 } 105 106 private static void validate(ConcurrentExecutables instance) { 107 checkNotNull(instance.executables, "executables cannot be null"); 108 checkNotNull(instance.uncaughtException, "uncaughtException cannot be null"); 109 } 110 } 111 112 private ConcurrentExecutables(Builder builder) { 113 this.executables = ImmutableList.copyOf(builder.executables); 114 this.skip = builder.skip; 115 this.timed = builder.timed; 116 } 117 118 @Override 119 public void execute() { 120 if (skip) { 121 logger.info("Skipping execution of {} executables", executables.size()); 122 return; 123 } 124 List<Thread> threads = getThreads(executables); 125 Stopwatch stopwatch = Stopwatch.createStarted(); 126 Threads.start(threads); 127 Threads.join(threads); 128 if (uncaughtException.isPresent()) { 129 throw uncaughtException.get(); 130 } 131 if (timed) { 132 logger.info("------------------------------------------------------------------------"); 133 logger.info("Total Time: {} (Wall Clock)", FormatUtils.getTime(stopwatch)); 134 logger.info("------------------------------------------------------------------------"); 135 } 136 } 137 138 protected List<Thread> getThreads(List<Executable> executables) { 139 List<Thread> threads = new ArrayList<Thread>(); 140 for (Executable executable : executables) { 141 Runnable runnable = new ExecutableRunner(executable); 142 Thread thread = new Thread(runnable, "Executable"); 143 thread.setUncaughtExceptionHandler(this); 144 threads.add(thread); 145 } 146 return threads; 147 } 148 149 @Override 150 public synchronized void uncaughtException(Thread thread, Throwable uncaughtException) { 151 // Only report back on the first uncaught exception reported by any thread 152 // Any exceptions after the first one get ignored 153 if (!this.uncaughtException.isPresent()) { 154 String context = "Exception in thread [" + thread.getId() + ":" + thread.getName() + "]"; 155 this.uncaughtException = Optional.of(new IllegalStateException(context, uncaughtException)); 156 } 157 } 158 159 public ImmutableList<Executable> getExecutables() { 160 return executables; 161 } 162 163 public boolean isSkip() { 164 return skip; 165 } 166 167 public boolean isTimed() { 168 return timed; 169 } 170 171 public Optional<IllegalStateException> getUncaughtException() { 172 return uncaughtException; 173 } 174 175}