package edu.umd.rhsmith.diads.meater.core.app.components.media; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.Map; import java.util.NoSuchElementException; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import edu.umd.rhsmith.diads.meater.core.app.MEaterInitializer; import edu.umd.rhsmith.diads.meater.core.app.MEaterMain; import edu.umd.rhsmith.diads.meater.util.ControlException; import edu.umd.rhsmith.diads.meater.util.ControlUnit; import edu.umd.rhsmith.diads.meater.util.Util; public class MediaManager extends ControlUnit { private final MEaterMain main; private final Map<String, MediaProcessor<?>> processors; private final Map<String, MediaSource<?>> sources; private final Map<MediaSource<?>, Collection<MediaProcessor<?>>> outputs; private final Map<MediaSource<?>, Collection<MediaProcessor<?>>> rejectable; private final ThreadPoolExecutor processingThreadPool; private final double rejectionThreshold; public MediaManager(MEaterInitializer init, MEaterMain main) { this.main = main; // media registrations this.processors = new HashMap<String, MediaProcessor<?>>(); this.sources = new HashMap<String, MediaSource<?>>(); this.outputs = new HashMap<MediaSource<?>, Collection<MediaProcessor<?>>>(); this.rejectable = new HashMap<MediaSource<?>, Collection<MediaProcessor<?>>>(); // create thread pool this.processingThreadPool = new ThreadPoolExecutor(init .getCoreThreadPoolSize(), init.getMaxThreadPoolSize(), init .getThreadPoolKeepAliveTimeS(), TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); this.rejectionThreshold = init.getResourceLimitRejectionThreshold(); // logging this.setLogger(this.main.getLogger()); this.setLogName("MediaManager"); } public <M> void submitMedia(M media, MediaSource<M> source) { // check that we're under utilization; otherwise, we may reject // certain outputs boolean reject = false; double utilization = Util.getMemoryUtilizationFraction(); if (utilization >= rejectionThreshold) { this.logFine(MSG_THRESHOLD_REJECTED_FMT, utilization, this.rejectionThreshold, media.toString()); reject = true; } // send the media to all outputs as their own tasks final M m = media; for (MediaProcessor<?> processor : this.outputs.get(source)) { // ...unless we're allowed to reject it if (reject && rejectable.get(source).contains(processor)) { continue; } // we already checked that things were okay when the output mapping // was registered, so we can suppress this warning @SuppressWarnings("unchecked") final MediaProcessor<? super M> p = (MediaProcessor<? super M>) processor; this.processingThreadPool.submit(new Runnable() { @Override public void run() { p.processMedia(m); } }); } } /* * -------------------------------- * General getters/setters * -------------------------------- */ public MEaterMain getMain() { return main; } /* * -------------------------------- * Media handler registration * -------------------------------- */ public <M> void registerSource(String ownerName, MediaSource<M> source) throws IllegalStateException { synchronized (this.controlLock) { this.requireUnStarted(); this.logInfo(MSG_SRC_REG_FMT, source.getSourceName()); this.sources.put(Media.handlerName(ownerName, source .getSourceName()), source); source.setMediaManager(this); } } public <M> void registerProcessor(String ownerName, MediaProcessor<M> processor) throws IllegalStateException { synchronized (this.controlLock) { this.requireUnStarted(); this.logInfo(MSG_PROC_REG_FMT, processor.getProcessorName()); this.processors.put(Media.handlerName(ownerName, processor .getProcessorName()), processor); } } public <M> void registerSource(MediaSource<M> source) throws IllegalStateException { this.registerSource(null, source); } public <M> void registerProcessor(MediaProcessor<M> processor) throws IllegalStateException { this.registerProcessor(null, processor); } public <M> void registerOutput(MediaSource<? extends M> source, MediaProcessor<? super M> output, boolean rejectable) throws IllegalStateException { synchronized (this.controlLock) { this.requireUnStarted(); this.logInfo(MSG_OUTPUT_REG_FMT, source.getSourceName(), output .getProcessorName()); Collection<MediaProcessor<?>> ps; ps = this.outputs.get(output); if (ps == null) { ps = new LinkedList<MediaProcessor<?>>(); this.outputs.put(source, ps); } ps.add(output); if (rejectable) { Collection<MediaProcessor<?>> rj; rj = this.outputs.get(output); if (rj == null) { rj = new HashSet<MediaProcessor<?>>(); this.rejectable.put(source, rj); } rj.add(output); } } } // we must cast generic parameters based on compatibility of media classes - // the compiler can't check this, but we do. @SuppressWarnings("unchecked") public <M> MediaSource<? extends M> getMediaSource(String ownerName, String sourceName, Class<? extends M> mediaclass) throws NoSuchElementException { String name = Media.handlerName(ownerName, sourceName); MediaSource<?> source = this.sources.get(name); if (source == null) { throw new NoSuchElementException(this.messageString( MSG_ERR_NO_SRC_FMT, name)); } if (mediaclass.isAssignableFrom(source.getMediaClass())) { return (MediaSource<? extends M>) source; } else { throw new NoSuchElementException(this.messageString( MSG_ERR_INCOMPATIBLE_SRC_FMT, name)); } } public <M> MediaSource<? extends M> getMediaSource(String sourceName, Class<? extends M> mediaclass) throws NoSuchElementException { return this.getMediaSource(null, sourceName, mediaclass); } // we must cast generic parameters based on compatibility of media classes - // the compiler can't check this, but we do. @SuppressWarnings("unchecked") public <M> MediaProcessor<? super M> getMediaProcessor(String ownerName, String processorName, Class<? extends M> mediaclass) throws NoSuchElementException { String name = Media.handlerName(ownerName, processorName); MediaProcessor<?> processor = this.processors.get(name); if (processor == null) { throw new NoSuchElementException(this.messageString( MSG_ERR_NO_PROC_FMT, name)); } if (processor.getMediaClass().isAssignableFrom(mediaclass)) { return (MediaProcessor<? super M>) processor; } else { throw new NoSuchElementException(this.messageString( MSG_ERR_INCOMPATIBLE_PROC_FMT, name)); } } public <M> MediaProcessor<? super M> getMediaProcessor( String processorName, Class<? extends M> mediaclass) throws NoSuchElementException { return this.getMediaProcessor(null, processorName, mediaclass); } /* * -------------------------------- * Control methods * -------------------------------- */ @Override protected void doStartupRoutine() throws ControlException { } @Override protected void doShutdownRoutine() { try { this.stopThreadPool(); } catch (InterruptedException e) { this.logWarning(MSG_WARN_POOL_INTERRUPTED); } } /* * -------------------------------- * Thread pool * -------------------------------- */ private void stopThreadPool() throws InterruptedException { this.logInfo(MSG_ENDING_POOL); this.processingThreadPool.shutdown(); this.processingThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); this.logInfo(MSG_POOL_TERMINATED); } /* * -------------------------------- * Messages * -------------------------------- */ private static final String MSG_OUTPUT_REG_FMT = "Output %s -> %s registered"; private static final String MSG_PROC_REG_FMT = "Media processor %s registered"; private static final String MSG_SRC_REG_FMT = "Media source %s registered"; private static final String MSG_POOL_TERMINATED = "Media processor thread-pool terminated"; private static final String MSG_ENDING_POOL = "Awaiting termination of media processor thread-pool"; private static final String MSG_WARN_POOL_INTERRUPTED = "Interrupted while awaiting termination of media processor thread-pool"; private static final String MSG_ERR_INCOMPATIBLE_SRC_FMT = "Media source %s uses media class %s, which is incompatible with requested type %s"; private static final String MSG_ERR_NO_SRC_FMT = "No media source exists with name %s"; private static final String MSG_ERR_INCOMPATIBLE_PROC_FMT = "Media processor %s uses media class %s, which is incompatible with requested type %s"; private static final String MSG_ERR_NO_PROC_FMT = "No media processor exists with name %s"; private static final String MSG_THRESHOLD_REJECTED_FMT = "Memory utilization at %d >= %d; media %s rejected"; }