001    package org.imgscalr;
002    
003    import java.awt.Color;
004    import java.awt.image.BufferedImage;
005    import java.awt.image.BufferedImageOp;
006    import java.awt.image.ImagingOpException;
007    import java.util.concurrent.Callable;
008    import java.util.concurrent.ExecutorService;
009    import java.util.concurrent.Executors;
010    import java.util.concurrent.Future;
011    import java.util.concurrent.ThreadFactory;
012    import java.util.concurrent.ThreadPoolExecutor;
013    import java.util.concurrent.TimeUnit;
014    import java.util.concurrent.atomic.AtomicInteger;
015    
016    import org.imgscalr.Scalr.Method;
017    import org.imgscalr.Scalr.Mode;
018    import org.imgscalr.Scalr.Rotation;
019    
020    /**
021     * Class used to provide the asynchronous versions of all the methods defined in
022     * {@link Scalr} for the purpose of efficiently handling large amounts of image
023     * operations via a select number of processing threads asynchronously.
024     * <p/>
025     * Given that image-scaling operations, especially when working with large
026     * images, can be very hardware-intensive (both CPU and memory), in large-scale
027     * deployments (e.g. a busy web application) it becomes increasingly important
028     * that the scale operations performed by imgscalr be manageable so as not to
029     * fire off too many simultaneous operations that the JVM's heap explodes and
030     * runs out of memory or pegs the CPU on the host machine, staving all other
031     * running processes.
032     * <p/>
033     * Up until now it was left to the caller to implement their own serialization
034     * or limiting logic to handle these use-cases. Given imgscalr's popularity in
035     * web applications it was determined that this requirement be common enough
036     * that it should be integrated directly into the imgscalr library for everyone
037     * to benefit from.
038     * <p/>
039     * Every method in this class wraps the matching methods in the {@link Scalr}
040     * class in new {@link Callable} instances that are submitted to an internal
041     * {@link ExecutorService} for execution at a later date. A {@link Future} is
042     * returned to the caller representing the task that is either currently
043     * performing the scale operation or will at a future date depending on where it
044     * is in the {@link ExecutorService}'s queue. {@link Future#get()} or
045     * {@link Future#get(long, TimeUnit)} can be used to block on the
046     * <code>Future</code>, waiting for the scale operation to complete and return
047     * the resultant {@link BufferedImage} to the caller.
048     * <p/>
049     * This design provides the following features:
050     * <ul>
051     * <li>Non-blocking, asynchronous scale operations that can continue execution
052     * while waiting on the scaled result.</li>
053     * <li>Serialize all scale requests down into a maximum number of
054     * <em>simultaneous</em> scale operations with no additional/complex logic. The
055     * number of simultaneous scale operations is caller-configurable (see
056     * {@link #THREAD_COUNT}) so as best to optimize the host system (e.g. 1 scale
057     * thread per core).</li>
058     * <li>No need to worry about overloading the host system with too many scale
059     * operations, they will simply queue up in this class and execute in-order.</li>
060     * <li>Synchronous/blocking behavior can still be achieved (if desired) by
061     * calling <code>get()</code> or <code>get(long, TimeUnit)</code> immediately on
062     * the returned {@link Future} from any of the methods below.</li>
063     * </ul>
064     * <h3>Performance</h3>
065     * When tuning this class for optimal performance, benchmarking your particular
066     * hardware is the best approach. For some rough guidelines though, there are
067     * two resources you want to watch closely:
068     * <ol>
069     * <li>JVM Heap Memory (Assume physical machine memory is always sufficiently
070     * large)</li>
071     * <li># of CPU Cores</li>
072     * </ol>
073     * You never want to allocate more scaling threads than you have CPU cores and
074     * on a sufficiently busy host where some of the cores may be busy running a
075     * database or a web server, you will want to allocate even less scaling
076     * threads.
077     * <p/>
078     * So as a maximum you would never want more scaling threads than CPU cores in
079     * any situation and less so on a busy server.
080     * <p/>
081     * If you allocate more threads than you have available CPU cores, your scaling
082     * operations will slow down as the CPU will spend a considerable amount of time
083     * context-switching between threads on the same core trying to finish all the
084     * tasks in parallel. You might still be tempted to do this because of the I/O
085     * delay some threads will encounter reading images off disk, but when you do
086     * your own benchmarking you'll likely find (as I did) that the actual disk I/O
087     * necessary to pull the image data off disk is a much smaller portion of the
088     * execution time than the actual scaling operations.
089     * <p/>
090     * If you are executing on a storage medium that is unexpectedly slow and I/O is
091     * a considerable portion of the scaling operation (e.g. S3 or EBS volumes),
092     * feel free to try using more threads than CPU cores to see if that helps; but
093     * in most normal cases, it will only slow down all other parallel scaling
094     * operations.
095     * <p/>
096     * As for memory, every time an image is scaled it is decoded into a
097     * {@link BufferedImage} and stored in the JVM Heap space (decoded image
098     * instances are always larger than the source images on-disk). For larger
099     * images, that can use up quite a bit of memory. You will need to benchmark
100     * your particular use-cases on your hardware to get an idea of where the sweet
101     * spot is for this; if you are operating within tight memory bounds, you may
102     * want to limit simultaneous scaling operations to 1 or 2 regardless of the
103     * number of cores just to avoid having too many {@link BufferedImage} instances
104     * in JVM Heap space at the same time.
105     * <p/>
106     * These are rough metrics and behaviors to give you an idea of how best to tune
107     * this class for your deployment, but nothing can replacement writing a small
108     * Java class that scales a handful of images in a number of different ways and
109     * testing that directly on your deployment hardware.
110     * <h3>Resource Overhead</h3>
111     * The {@link ExecutorService} utilized by this class won't be initialized until
112     * one of the operation methods are called, at which point the
113     * <code>service</code> will be instantiated for the first time and operation
114     * queued up.
115     * <p/>
116     * More specifically, if you have no need for asynchronous image processing
117     * offered by this class, you don't need to worry about wasted resources or
118     * hanging/idle threads as they will never be created if you never use this
119     * class.
120     * <h3>Cleaning up Service Threads</h3>
121     * By default the {@link Thread}s created by the internal
122     * {@link ThreadPoolExecutor} do not run in <code>daemon</code> mode; which
123     * means they will block the host VM from exiting until they are explicitly shut
124     * down in a client application; in a server application the container will shut
125     * down the pool forcibly.
126     * <p/>
127     * If you have used the {@link AsyncScalr} class and are trying to shut down a
128     * client application, you will need to call {@link #getService()} then
129     * {@link ExecutorService#shutdown()} or {@link ExecutorService#shutdownNow()}
130     * to have the threads terminated; you may also want to look at the
131     * {@link ExecutorService#awaitTermination(long, TimeUnit)} method if you'd like
132     * to more closely monitor the shutting down process (and finalization of
133     * pending scale operations).
134     * <h3>Reusing Shutdown AsyncScalr</h3>
135     * If you have previously called <code>shutdown</code> on the underlying service
136     * utilized by this class, subsequent calls to any of the operations this class
137     * provides will invoke the internal {@link #checkService()} method which will
138     * replace the terminated underlying {@link ExecutorService} with a new one via
139     * the {@link #createService()} method.
140     * <h3>Custom Implementations</h3>
141     * If a subclass wants to customize the {@link ExecutorService} or
142     * {@link ThreadFactory} used under the covers, this can be done by overriding
143     * the {@link #createService()} method which is invoked by this class anytime a
144     * new {@link ExecutorService} is needed.
145     * <p/>
146     * By default the {@link #createService()} method delegates to the
147     * {@link #createService(ThreadFactory)} method with a new instance of
148     * {@link DefaultThreadFactory}. Either of these methods can be overridden and
149     * customized easily if desired.
150     * <p/>
151     * <strong>TIP</strong>: A common customization to this class is to make the
152     * {@link Thread}s generated by the underlying factory more server-friendly, in
153     * which case the caller would want to use an instance of the
154     * {@link ServerThreadFactory} when creating the new {@link ExecutorService}.
155     * <p/>
156     * This can be done in one line by overriding {@link #createService()} and
157     * returning the result of:
158     * <code>return createService(new ServerThreadFactory());</code>
159     * <p/>
160     * By default this class uses an {@link ThreadPoolExecutor} internally to handle
161     * execution of queued image operations. If a different type of
162     * {@link ExecutorService} is desired, again, simply overriding the
163     * {@link #createService()} method of choice is the right way to do that.
164     * 
165     * @author Riyad Kalla (software@thebuzzmedia.com)
166     * @since 3.2
167     */
168    @SuppressWarnings("javadoc")
169    public class AsyncScalr {
170            /**
171             * System property name used to set the number of threads the default
172             * underlying {@link ExecutorService} will use to process async image
173             * operations.
174             * <p/>
175             * Value is "<code>imgscalr.async.threadCount</code>".
176             */
177            public static final String THREAD_COUNT_PROPERTY_NAME = "imgscalr.async.threadCount";
178    
179            /**
180             * Number of threads the internal {@link ExecutorService} will use to
181             * simultaneously execute scale requests.
182             * <p/>
183             * This value can be changed by setting the
184             * <code>imgscalr.async.threadCount</code> system property (see
185             * {@link #THREAD_COUNT_PROPERTY_NAME}) to a valid integer value &gt; 0.
186             * <p/>
187             * Default value is <code>2</code>.
188             */
189            public static final int THREAD_COUNT = Integer.getInteger(
190                            THREAD_COUNT_PROPERTY_NAME, 2);
191    
192            /**
193             * Initializer used to verify the THREAD_COUNT system property.
194             */
195            static {
196                    if (THREAD_COUNT < 1)
197                            throw new RuntimeException("System property '"
198                                            + THREAD_COUNT_PROPERTY_NAME + "' set THREAD_COUNT to "
199                                            + THREAD_COUNT + ", but THREAD_COUNT must be > 0.");
200            }
201    
202            protected static ExecutorService service;
203    
204            /**
205             * Used to get access to the internal {@link ExecutorService} used by this
206             * class to process scale operations.
207             * <p/>
208             * <strong>NOTE</strong>: You will need to explicitly shutdown any service
209             * currently set on this class before the host JVM exits.
210             * <p/>
211             * You can call {@link ExecutorService#shutdown()} to wait for all scaling
212             * operations to complete first or call
213             * {@link ExecutorService#shutdownNow()} to kill any in-process operations
214             * and purge all pending operations before exiting.
215             * <p/>
216             * Additionally you can use
217             * {@link ExecutorService#awaitTermination(long, TimeUnit)} after issuing a
218             * shutdown command to try and wait until the service has finished all
219             * tasks.
220             * 
221             * @return the current {@link ExecutorService} used by this class to process
222             *         scale operations.
223             */
224            public static ExecutorService getService() {
225                    return service;
226            }
227    
228            /**
229             * @see Scalr#apply(BufferedImage, BufferedImageOp...)
230             */
231            public static Future<BufferedImage> apply(final BufferedImage src,
232                            final BufferedImageOp... ops) throws IllegalArgumentException,
233                            ImagingOpException {
234                    checkService();
235    
236                    return service.submit(new Callable<BufferedImage>() {
237                            public BufferedImage call() throws Exception {
238                                    return Scalr.apply(src, ops);
239                            }
240                    });
241            }
242    
243            /**
244             * @see Scalr#crop(BufferedImage, int, int, BufferedImageOp...)
245             */
246            public static Future<BufferedImage> crop(final BufferedImage src,
247                            final int width, final int height, final BufferedImageOp... ops)
248                            throws IllegalArgumentException, ImagingOpException {
249                    checkService();
250    
251                    return service.submit(new Callable<BufferedImage>() {
252                            public BufferedImage call() throws Exception {
253                                    return Scalr.crop(src, width, height, ops);
254                            }
255                    });
256            }
257    
258            /**
259             * @see Scalr#crop(BufferedImage, int, int, int, int, BufferedImageOp...)
260             */
261            public static Future<BufferedImage> crop(final BufferedImage src,
262                            final int x, final int y, final int width, final int height,
263                            final BufferedImageOp... ops) throws IllegalArgumentException,
264                            ImagingOpException {
265                    checkService();
266    
267                    return service.submit(new Callable<BufferedImage>() {
268                            public BufferedImage call() throws Exception {
269                                    return Scalr.crop(src, x, y, width, height, ops);
270                            }
271                    });
272            }
273    
274            /**
275             * @see Scalr#pad(BufferedImage, int, BufferedImageOp...)
276             */
277            public static Future<BufferedImage> pad(final BufferedImage src,
278                            final int padding, final BufferedImageOp... ops)
279                            throws IllegalArgumentException, ImagingOpException {
280                    checkService();
281    
282                    return service.submit(new Callable<BufferedImage>() {
283                            public BufferedImage call() throws Exception {
284                                    return Scalr.pad(src, padding, ops);
285                            }
286                    });
287            }
288    
289            /**
290             * @see Scalr#pad(BufferedImage, int, Color, BufferedImageOp...)
291             */
292            public static Future<BufferedImage> pad(final BufferedImage src,
293                            final int padding, final Color color, final BufferedImageOp... ops)
294                            throws IllegalArgumentException, ImagingOpException {
295                    checkService();
296    
297                    return service.submit(new Callable<BufferedImage>() {
298                            public BufferedImage call() throws Exception {
299                                    return Scalr.pad(src, padding, color, ops);
300                            }
301                    });
302            }
303    
304            /**
305             * @see Scalr#resize(BufferedImage, int, BufferedImageOp...)
306             */
307            public static Future<BufferedImage> resize(final BufferedImage src,
308                            final int targetSize, final BufferedImageOp... ops)
309                            throws IllegalArgumentException, ImagingOpException {
310                    checkService();
311    
312                    return service.submit(new Callable<BufferedImage>() {
313                            public BufferedImage call() throws Exception {
314                                    return Scalr.resize(src, targetSize, ops);
315                            }
316                    });
317            }
318    
319            /**
320             * @see Scalr#resize(BufferedImage, Method, int, BufferedImageOp...)
321             */
322            public static Future<BufferedImage> resize(final BufferedImage src,
323                            final Method scalingMethod, final int targetSize,
324                            final BufferedImageOp... ops) throws IllegalArgumentException,
325                            ImagingOpException {
326                    checkService();
327    
328                    return service.submit(new Callable<BufferedImage>() {
329                            public BufferedImage call() throws Exception {
330                                    return Scalr.resize(src, scalingMethod, targetSize, ops);
331                            }
332                    });
333            }
334    
335            /**
336             * @see Scalr#resize(BufferedImage, Mode, int, BufferedImageOp...)
337             */
338            public static Future<BufferedImage> resize(final BufferedImage src,
339                            final Mode resizeMode, final int targetSize,
340                            final BufferedImageOp... ops) throws IllegalArgumentException,
341                            ImagingOpException {
342                    checkService();
343    
344                    return service.submit(new Callable<BufferedImage>() {
345                            public BufferedImage call() throws Exception {
346                                    return Scalr.resize(src, resizeMode, targetSize, ops);
347                            }
348                    });
349            }
350    
351            /**
352             * @see Scalr#resize(BufferedImage, Method, Mode, int, BufferedImageOp...)
353             */
354            public static Future<BufferedImage> resize(final BufferedImage src,
355                            final Method scalingMethod, final Mode resizeMode,
356                            final int targetSize, final BufferedImageOp... ops)
357                            throws IllegalArgumentException, ImagingOpException {
358                    checkService();
359    
360                    return service.submit(new Callable<BufferedImage>() {
361                            public BufferedImage call() throws Exception {
362                                    return Scalr.resize(src, scalingMethod, resizeMode, targetSize,
363                                                    ops);
364                            }
365                    });
366            }
367    
368            /**
369             * @see Scalr#resize(BufferedImage, int, int, BufferedImageOp...)
370             */
371            public static Future<BufferedImage> resize(final BufferedImage src,
372                            final int targetWidth, final int targetHeight,
373                            final BufferedImageOp... ops) throws IllegalArgumentException,
374                            ImagingOpException {
375                    checkService();
376    
377                    return service.submit(new Callable<BufferedImage>() {
378                            public BufferedImage call() throws Exception {
379                                    return Scalr.resize(src, targetWidth, targetHeight, ops);
380                            }
381                    });
382            }
383    
384            /**
385             * @see Scalr#resize(BufferedImage, Method, int, int, BufferedImageOp...)
386             */
387            public static Future<BufferedImage> resize(final BufferedImage src,
388                            final Method scalingMethod, final int targetWidth,
389                            final int targetHeight, final BufferedImageOp... ops) {
390                    checkService();
391    
392                    return service.submit(new Callable<BufferedImage>() {
393                            public BufferedImage call() throws Exception {
394                                    return Scalr.resize(src, scalingMethod, targetWidth,
395                                                    targetHeight, ops);
396                            }
397                    });
398            }
399    
400            /**
401             * @see Scalr#resize(BufferedImage, Mode, int, int, BufferedImageOp...)
402             */
403            public static Future<BufferedImage> resize(final BufferedImage src,
404                            final Mode resizeMode, final int targetWidth,
405                            final int targetHeight, final BufferedImageOp... ops)
406                            throws IllegalArgumentException, ImagingOpException {
407                    checkService();
408    
409                    return service.submit(new Callable<BufferedImage>() {
410                            public BufferedImage call() throws Exception {
411                                    return Scalr.resize(src, resizeMode, targetWidth, targetHeight,
412                                                    ops);
413                            }
414                    });
415            }
416    
417            /**
418             * @see Scalr#resize(BufferedImage, Method, Mode, int, int,
419             *      BufferedImageOp...)
420             */
421            public static Future<BufferedImage> resize(final BufferedImage src,
422                            final Method scalingMethod, final Mode resizeMode,
423                            final int targetWidth, final int targetHeight,
424                            final BufferedImageOp... ops) throws IllegalArgumentException,
425                            ImagingOpException {
426                    checkService();
427    
428                    return service.submit(new Callable<BufferedImage>() {
429                            public BufferedImage call() throws Exception {
430                                    return Scalr.resize(src, scalingMethod, resizeMode,
431                                                    targetWidth, targetHeight, ops);
432                            }
433                    });
434            }
435    
436            /**
437             * @see Scalr#rotate(BufferedImage, Rotation, BufferedImageOp...)
438             */
439            public static Future<BufferedImage> rotate(final BufferedImage src,
440                            final Rotation rotation, final BufferedImageOp... ops)
441                            throws IllegalArgumentException, ImagingOpException {
442                    checkService();
443    
444                    return service.submit(new Callable<BufferedImage>() {
445                            public BufferedImage call() throws Exception {
446                                    return Scalr.rotate(src, rotation, ops);
447                            }
448                    });
449            }
450    
451            protected static ExecutorService createService() {
452                    return createService(new DefaultThreadFactory());
453            }
454    
455            protected static ExecutorService createService(ThreadFactory factory)
456                            throws IllegalArgumentException {
457                    if (factory == null)
458                            throw new IllegalArgumentException("factory cannot be null");
459    
460                    return Executors.newFixedThreadPool(THREAD_COUNT, factory);
461            }
462    
463            /**
464             * Used to verify that the underlying <code>service</code> points at an
465             * active {@link ExecutorService} instance that can be used by this class.
466             * <p/>
467             * If <code>service</code> is <code>null</code>, has been shutdown or
468             * terminated then this method will replace it with a new
469             * {@link ExecutorService} by calling the {@link #createService()} method
470             * and assigning the returned value to <code>service</code>.
471             * <p/>
472             * Any subclass that wants to customize the {@link ExecutorService} or
473             * {@link ThreadFactory} used internally by this class should override the
474             * {@link #createService()}.
475             */
476            protected static void checkService() {
477                    if (service == null || service.isShutdown() || service.isTerminated()) {
478                            /*
479                             * If service was shutdown or terminated, assigning a new value will
480                             * free the reference to the instance, allowing it to be GC'ed when
481                             * it is done shutting down (assuming it hadn't already).
482                             */
483                            service = createService();
484                    }
485            }
486    
487            /**
488             * Default {@link ThreadFactory} used by the internal
489             * {@link ExecutorService} to creates execution {@link Thread}s for image
490             * scaling.
491             * <p/>
492             * More or less a copy of the hidden class backing the
493             * {@link Executors#defaultThreadFactory()} method, but exposed here to make
494             * it easier for implementors to extend and customize.
495             * 
496             * @author Doug Lea
497             * @author Riyad Kalla (software@thebuzzmedia.com)
498             * @since 4.0
499             */
500            protected static class DefaultThreadFactory implements ThreadFactory {
501                    protected static final AtomicInteger poolNumber = new AtomicInteger(1);
502    
503                    protected final ThreadGroup group;
504                    protected final AtomicInteger threadNumber = new AtomicInteger(1);
505                    protected final String namePrefix;
506    
507                    DefaultThreadFactory() {
508                            SecurityManager manager = System.getSecurityManager();
509    
510                            /*
511                             * Determine the group that threads created by this factory will be
512                             * in.
513                             */
514                            group = (manager == null ? Thread.currentThread().getThreadGroup()
515                                            : manager.getThreadGroup());
516    
517                            /*
518                             * Define a common name prefix for the threads created by this
519                             * factory.
520                             */
521                            namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
522                    }
523    
524                    /**
525                     * Used to create a {@link Thread} capable of executing the given
526                     * {@link Runnable}.
527                     * <p/>
528                     * Thread created by this factory are utilized by the parent
529                     * {@link ExecutorService} when processing queued up scale operations.
530                     */
531                    public Thread newThread(Runnable r) {
532                            /*
533                             * Create a new thread in our specified group with a meaningful
534                             * thread name so it is easy to identify.
535                             */
536                            Thread thread = new Thread(group, r, namePrefix
537                                            + threadNumber.getAndIncrement(), 0);
538    
539                            // Configure thread according to class or subclass
540                            thread.setDaemon(false);
541                            thread.setPriority(Thread.NORM_PRIORITY);
542    
543                            return thread;
544                    }
545            }
546    
547            /**
548             * An extension of the {@link DefaultThreadFactory} class that makes two
549             * changes to the execution {@link Thread}s it generations:
550             * <ol>
551             * <li>Threads are set to be daemon threads instead of user threads.</li>
552             * <li>Threads execute with a priority of {@link Thread#MIN_PRIORITY} to
553             * make them more compatible with server environment deployments.</li>
554             * </ol>
555             * This class is provided as a convenience for subclasses to use if they
556             * want this (common) customization to the {@link Thread}s used internally
557             * by {@link AsyncScalr} to process images, but don't want to have to write
558             * the implementation.
559             * 
560             * @author Riyad Kalla (software@thebuzzmedia.com)
561             * @since 4.0
562             */
563            protected static class ServerThreadFactory extends DefaultThreadFactory {
564                    /**
565                     * Overridden to set <code>daemon</code> property to <code>true</code>
566                     * and decrease the priority of the new thread to
567                     * {@link Thread#MIN_PRIORITY} before returning it.
568                     */
569                    @Override
570                    public Thread newThread(Runnable r) {
571                            Thread thread = super.newThread(r);
572    
573                            thread.setDaemon(true);
574                            thread.setPriority(Thread.MIN_PRIORITY);
575    
576                            return thread;
577                    }
578            }
579    }