/****************************************************************************** * Product: iDempiere ERP & CRM Smart Business Solution * * This program is free software; you can redistribute it and/or modify it * * under the terms version 2 of the GNU General Public License as published * * by the Free Software Foundation. This program is distributed in the hope * * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * * with this program; if not, write to the Free Software Foundation, Inc., * * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * *****************************************************************************/ package org.adempiere.base; import java.time.Duration; import java.time.Instant; import java.util.Properties; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import org.compiere.Adempiere; import org.compiere.util.CLogger; import org.compiere.util.Ini; import io.github.classgraph.ClassGraph.FailureHandler; /** * Base class for factories that discover their classes by means of annotation scanning. * @author Saulo Gil */ public abstract class AnnotationBasedFactory { private boolean scanSuccessful; private final AtomicBoolean scanCompleted = new AtomicBoolean(false); /** * Bogus {@link Future} used only to block the calling thread. * @see #blockWhileScanning() */ private CompletableFuture threadBlockerFuture = new CompletableFuture<>(); /** * {@link Executor} to be used for asynchronous class scanning. It never gets shut down * since there's no way to tell when it wouldn't be needed anymore. However, footprint * should be minimal when unused. */ private static final ExecutorService executorService = Executors.newCachedThreadPool(); private final static CLogger s_log = CLogger.getCLogger(AnnotationBasedFactory.class); private final FailureHandler defaultScanFailureHandler = throwable -> { s_log.severe(throwable.getMessage()); signalScanCompletion(false); }; protected void signalScanCompletion(boolean succeeded) { scanSuccessful = succeeded; scanCompleted.set(true); try { threadBlockerFuture.complete(null); } catch (Exception e) { } } /** * Wait for completion of annotation scanning */ protected void blockWhileScanning() { String className = this.getClass().getSimpleName(); if(!scanCompleted.get()) try { Instant start = Instant.now(); threadBlockerFuture.get(); if (s_log.isLoggable(Level.FINE)) s_log.fine(() -> String.format("%s waited %d(ms) for class scanning to end" , className, Duration.between(start, Instant.now()).toMillis())); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } if(!scanSuccessful) throw new RuntimeException(className + " - annotation scan unsuccessful"); } protected ExecutorService getExecutorService() { return executorService; } /** * Suggests a maximum amount of threads to be allocated to annotation scanning * for each individual factory. * @return suggested maximum amount of threads * @see #executorService * @see Adempiere#getThreadPoolExecutor() */ protected int getMaxThreads() { int max = Runtime.getRuntime().availableProcessors() * 5; int defaultMax = max; Properties properties = Ini.getProperties(); String maxSize = properties.getProperty("MaxThreadPoolSize"); if (maxSize != null) { try { max = Integer.parseInt(maxSize) / 5; } catch (Exception e) {} } if (max <= 0) max = defaultMax; return max; } protected FailureHandler getScanFailureHandler() { return defaultScanFailureHandler; } }