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}