Spring – Java thumbnail generation based on content type

The purpose of this tutorial is to demonstrate how to generate thumbnails in Java using the Spring framework.

The thumbnail generation is based on the algorithm by Phil Reeve .

It has also been further improved by Jim McCabe to base the thumbnail generation in previously generated thumbnail so that the image is not continuously evaluated.

The code included contains also a file upload form that demonstrates uploading files with Spring MVC although you can use the ThumbnailGeneratorEngine in any of your service beans.

This tutorial also uses the Engine / Processor pattern as explained here.

The ThumbnailGeneratorEngine can be configured with processors that know how to process certain content types and input streams. The code only demonstrates generating thumbnails from images but other libraries can be used to implement processors that generate thumbnails from pdf, word, excel, urls, etc…

The pattern allows to add as many processors as necessary by plugin them into the spring configuration and associating them to content types in the engine configuration.

This tutorial is available for download with svn:
svn checkout http://raulrajatutorials.googlecode.com/svn/trunk/ raulrajatutorials-read-only
Comments in the code itself should be self-explanatory if you already have some java + spring experience.

And now to the point.

First a video of the actual code at work and then the code

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"
       default-autowire="byName">
 
    <!-- thumbnail generator engine -->
    <bean id="thumbnailGeneratorEngine" class="com.raulraja.util.thumbnails.impl.ThumbnailGeneratorEngineImpl">
 
        <!-- the file extension for the thumbnail files -->
		<property name="generatedExtension">
			<value>.jpg</value>
		</property>
 
        <!-- the different sizes we want to generate, adjusting the aspect ratio based on the biggest dimension -->
		<property name="supportedSizes">
			<list>
                <value>900</value>
                <value>768</value>
                <value>375</value>
                <value>128</value>
                <value>64</value>
                <value>48</value>
                <value>22</value>
			</list>
		</property>
 
        <!-- mappings from the different content types to the right generator that handles each type -->
		<property name="thumbnailGenerators">
			<map>
				<entry key="image/jpeg" value-ref="imageThumbnailGenerator" />
                <entry key="image/jpg" value-ref="imageThumbnailGenerator" />
                <entry key="image/pjpeg" value-ref="imageThumbnailGenerator" />
                <entry key="image/gif" value-ref="imageThumbnailGenerator" />
                <entry key="image/png" value-ref="imageThumbnailGenerator" />
                <entry key="image/tiff" value-ref="imageThumbnailGenerator" />
                <entry key="image/bmp" value-ref="imageThumbnailGenerator" />
                <entry key="application/pdf" value-ref="pdfThumbnailGenerator" />
			</map>
		</property>
 
        <!-- A default thumbnail generator to be used for unregistered mime types -->
        <property name="defaultThumbnailGenerator" ref="imageThumbnailGenerator"/>
 
        <!-- location for the generated thumbnails -->
        <property name="thumbnailsLocation" value="/Users/raul/Pictures/test/"/>
	</bean>
 
    <!-- a thumbnail generator that generates thumbnails from images -->
	<bean id="imageThumbnailGenerator" class="com.raulraja.util.thumbnails.impl.ImageThumbnailGeneratorImpl" autowire="autodetect" />
 
    <!-- a thumbnail generator that generates thumbnails from pdf files not implemented for simplicity -->
	<bean id="pdfThumbnailGenerator" class="com.raulraja.util.thumbnails.impl.PDFThumbnailGeneratorImpl" autowire="autodetect" />
 
    <!-- upload code below for demo purposes -->
 
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- one of the properties available; the maximum file size in bytes -->
        <property name="maxUploadSize" value="50000000"/>
    </bean>
 
    <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <value>
                /upload.form=fileUploader
            </value>
        </property>
        <property name="alwaysUseFullPath" value="true" />
    </bean>
 
 
    <bean id="fileUploader" class="com.raulraja.util.upload.FileUploadService" autowire="autodetect" scope="session">
        <property name="commandClass" value="com.raulraja.util.upload.FileUpload"/>
        <property name="formView" value="uploadFile.jsp"/>
        <property name="successView" value="success.jsp" />
    </bean>
 
</beans>

web.xml (for demo purposes)

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
		  http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">
    <description>method-profiling-with-spring</description>
    <display-name>method-profiling-with-spring</display-name>
 
    <!-- Spring config  -->
    <context-param>
           <param-name>contextConfigLocation</param-name>
           <param-value>
               /WEB-INF/conf/applicationContext.xml
           </param-value>
       </context-param>
 
    <context-param>
        <param-name>log4jConfigLocation</param-name>
        <param-value>/WEB-INF/conf/log4j.properties</param-value>
    </context-param>
 
 
    <!-- Spring Listeners -->
    <listener>
        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    </listener>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
    </listener>
 
    <servlet>
        <servlet-name>springDispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
 
    <!-- maps the dispatcher to *.form -->
    <servlet-mapping>
        <servlet-name>springDispatcher</servlet-name>
        <url-pattern>*.form</url-pattern>
    </servlet-mapping>
 
</web-app>

uploadFile.jsp (for demo purposes)

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
    <title></title>
</head>
<body>
    <form action="upload.form" enctype="multipart/form-data" method="post">
        <input type="file" name="file" id="file"  />   
        <input type="submit" value="Upload"/>
    </form>
</body>
</html>

success.jsp (for demo purposes)

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title></title>
</head>
<body>
    sucess!!!
</body>
</html>

ThumbnailGeneratorEngine.java
(This is the interface and entry point for services to ask for thumbnails to be generated)

package com.raulraja.util.thumbnails;
 
import java.io.InputStream;
 
/**
 * An engine in charge of generating thumbnails for files
 */
public interface ThumbnailGeneratorEngine {
 
	/**
	 * @param fileNamePrefix the prefix for the generated thumbnails
	 * @param inputStream	the stream to generate thumbnails for
	 * @param contentType	the content type of this input stream for example image/jpeg
	 */
	void generateThumbnails(String fileNamePrefix, InputStream inputStream, String contentType);
}

ThumbnailGeneratorImpl.java
(The implementation for the thumbnail generator engine that is configured in the spring configuration)

package com.raulraja.util.thumbnails.impl;
 
import com.raulraja.util.thumbnails.ThumbnailGenerator;
import com.raulraja.util.thumbnails.ThumbnailGeneratorEngine;
import org.apache.log4j.Logger;
 
import java.io.File;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
 
/**
 * Default impl for the Thumbnail generator engine
 */
public class ThumbnailGeneratorEngineImpl implements ThumbnailGeneratorEngine {
 
	private final static Logger log = Logger.getLogger(ThumbnailGeneratorEngineImpl.class);
 
	private String generatedExtension;
 
	/**
	 * @param generatedExtension The extension for the generated thumbnails
	 */
	public void setGeneratedExtension(String generatedExtension) {
		this.generatedExtension = generatedExtension;
	}
 
	public String getGeneratedExtension() {
		return generatedExtension;
	}
 
	private Map<String, ThumbnailGenerator> thumbnailGenerators;
 
	/**
	 * @param thumbnailGenerators The thumbnail generators known by this engine mapped to a content type
	 */
	public void setThumbnailGenerators(Map<String, ThumbnailGenerator> thumbnailGenerators) {
		this.thumbnailGenerators = thumbnailGenerators;
	}
 
	private List<Integer> supportedSizes;
 
	/**
	 * @param supportedSizes The suported sizes for the batch of generated thumbs
	 */
	public void setSupportedSizes(List<Integer> supportedSizes) {
		this.supportedSizes = supportedSizes;
	}
 
	private ThumbnailGenerator defaultThumbnailGenerator;
 
	/**
	 * @param defaultThumbnailGenerator the default thumbnail generator to be used for unregistered mime types
	 */
	public void setDefaultThumbnailGenerator(ThumbnailGenerator defaultThumbnailGenerator) {
		this.defaultThumbnailGenerator = defaultThumbnailGenerator;
	}
 
	private String thumbnailsLocation;
 
	/**
	 * @param thumbnailsLocation location for the generated thumbnails
	 */
	public void setThumbnailsLocation(String thumbnailsLocation) {
		this.thumbnailsLocation = thumbnailsLocation;
	}
 
	/**
	 * @param fileNamePrefix the prefix for the generated thumbnails
	 * @param inputStream	the stream to generate thumbnails for
	 * @param contentType	the content type of this input stream for example image/jpeg
	 */
	public void generateThumbnails(String fileNamePrefix, InputStream inputStream, String contentType) {
		ThumbnailGenerator thumbnailGenerator = thumbnailGenerators.get(contentType);
		thumbnailGenerator = thumbnailGenerator != null ? thumbnailGenerator : defaultThumbnailGenerator;
		if (thumbnailGenerator != null) {
			Object hint = null;
			for (int dimension : supportedSizes) {
				File fileOut = new File(thumbnailsLocation, fileNamePrefix + "_" + dimension + generatedExtension);
				try {
					hint = thumbnailGenerator.createThumbnail(inputStream, fileOut, dimension, hint);
					log.debug("Generated thumbnail for: " + inputStream + " in " + fileOut + " for type " + contentType);
				}
				catch (Exception e) {
					log.error("Error generating thumbnail for: " + inputStream + " in " + fileOut + " for type " + contentType, e);
				}
 
			}
		} else {
			log.warn("Thumbnail generator not found for content type: " + contentType + " and no default generator was provided");
		}
	}
}

ThumbnailGenerator.java
(This is the interface the different generators implement)

package com.raulraja.util.thumbnails;
 
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
 
 
/**
 * Interface implemented by all thumbnail generators.
 *
 * <p>The thumbnail generation process is always performed in order with the largest thumbnails first.
 * Therefore it may be very handy for the generator to create smaller thumbnails using the results from
 * the prior iteration instead of always using the full-sized image as the source.  To accomplish this,
 * the generator can return a "hint" object that it can use in subsequent iterations, containing whatever
 * helpers it might want (such as the prior image already loaded in memory, etc).
 */
public interface ThumbnailGenerator {
 
 
 
    /**
     * Create the thumbnail.  The thumbnail should always save as a JPEG file.
     *
     * @param inputStream       The source data.
     * @param fileOut           The output file.
     * @param largestDimension  The max width and height.  The generator should size the thumbnail so
     *                          that the width and height both stay within this limit.
     * @param hint              Optional hint that was returned from the prior thumbnail generation
     *                          on this same file, null if none was returned or if this is the first
     *                          thumbnail in this context.
     *
     * @return an optional hint object that will be passed to subsequent thumbnail generation calls
     *         for this same source data.  Return null if you don't use hints, otherwise return some
     *         object which allows you to communicate extra information to the next round, such as
     *         the scaled image already loaded.
	 * @throws java.io.IOException if something goes wrong handling the io
     */
	public Object createThumbnail(InputStream inputStream, File fileOut, int largestDimension, Object hint) throws IOException;
 
}

AbstractThumbnailGenerator.java
(An abstract class with a utility method to save images as jpeg for all thumbnail generators to extend)

 
package com.raulraja.util.thumbnails.impl;
 
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
import org.apache.log4j.Logger;
 
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.IOException;
 
/**
 * Class providing convenience method for abstract thumbnail generators
 */
public class AbstractThumbnailGenerator {
 
	private final static Logger log = Logger.getLogger(AbstractThumbnailGenerator.class);
 
	/**
     * Save an image as a JPEG file on disk.
     *
     * @param image     The raw image to save.
     * @param fileOut   The location where you want to save the file.
     *
     * @return true if successful, false if unsuccessful.
	 * @throws java.io.IOException if something goes wrong closing the stream
     */
    public boolean saveImageAsJPEG(BufferedImage image, File fileOut) throws IOException {
        OutputStream streamOut = null;
        boolean      bSuccess = false;
 
        try {
            streamOut = new FileOutputStream(fileOut);
            JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(streamOut);
            encoder.encode(image);
            bSuccess = true;
        }
        catch (Throwable t) {
            log.warn("Files.saveImageAsJPEG(" + fileOut + "): " + t, t);
        }
        finally {
            if (streamOut != null) {
				streamOut.close();
			}
        }
 
        return bSuccess;
    }
 
 
}

ImageThumbnailGeneratorImpl.java
(A concrete implementation a thumbnail generator that is handling multiple image content types)

package com.raulraja.util.thumbnails.impl;
 
 
import com.raulraja.util.thumbnails.ThumbnailGenerator;
import org.apache.log4j.Logger;
 
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
 
 
/**
 * Generate thumbnails for images.
 */
public class ImageThumbnailGeneratorImpl extends AbstractThumbnailGenerator implements ThumbnailGenerator {
 
    private final static Logger log = Logger.getLogger(ImageThumbnailGeneratorImpl.class);
 
	public Object createThumbnail(InputStream inputStream, File fileOut, int largestDimension, Object hint) throws IOException {
 
        // What's the base image that we are starting with?  If there's a hint, that's the scaled image
        // from the last time around, use that... (since we know we always iterate downwards in scale)
        Image imageIn;
        if (hint instanceof Image) {
            imageIn = (Image) hint;
            log.info("createThumbnail(" + fileOut + ") reusing prior result image...");
        }
        else {
            log.info("createThumbnail(" + fileOut + ") reading image from stream " + inputStream);
            imageIn = ImageIO.read(inputStream);
        }
 
        if (imageIn == null) {
            log.warn("Could not read image file: " + inputStream);
            return hint;
        }
 
        BufferedImage imageOut = createThumbnailImage(imageIn, fileOut, largestDimension);
 
        // Return this image now as the hint for the next scaling iteration
        if (imageOut != null)
            hint = imageOut;
 
        return hint;
    }
 
 
    /**
     * Create a thumbnail image and save it to disk.
     *
     * This algorithm is based on:
     *      http://www.philreeve.com/java_high_quality_thumbnails.php
     *
     * @param imageIn           The image you want to scale.
     * @param fileOut           The output file.
     * @param largestDimension  The largest dimension, so that neither the width nor height
     *                          will exceed this value.
     *
     * @return the image that was created, null if imageIn or fileOut is null.
	 * @throws java.io.IOException if something goes wrong when saving as jpeg
     */
    public BufferedImage createThumbnailImage(Image imageIn, File fileOut, int largestDimension) throws IOException {
        if ((imageIn == null) || (fileOut == null))
            return null;
 
        //it seems to not return the right size until the methods get called for the first time
        imageIn.getWidth(null);
        imageIn.getHeight(null);
 
        // Find biggest dimension
        int     nImageWidth = imageIn.getWidth(null);
        int     nImageHeight = imageIn.getHeight(null);
        int     nImageLargestDim = Math.max(nImageWidth, nImageHeight);
        double  scale = (double) largestDimension / (double) nImageLargestDim;
        int     sizeDifference = nImageLargestDim - largestDimension;
 
        //create an image buffer to draw to
        BufferedImage imageOut = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); // 8-bit RGB
        Graphics2D g2d;
        AffineTransform tx;
 
        // Use a few steps if the sizes are drastically different, and only scale
        // if the desired size is smaller than the original.
        int numSteps = 0;
        if (scale < 1.0d) {
            // Make sure we have at least 1 step
            numSteps = Math.max(1, (sizeDifference / 100));
        }
 
        if (numSteps > 0) {
            int stepSize = sizeDifference / numSteps;
            int stepWeight = stepSize / 2;
            int heavierStepSize = stepSize + stepWeight;
            int lighterStepSize = stepSize - stepWeight;
            int currentStepSize, centerStep;
            double scaledW = imageIn.getWidth(null);
            double scaledH = imageIn.getHeight(null);
 
            if ((numSteps % 2) == 1) //if there's an odd number of steps
                centerStep = (int) Math.ceil((double) numSteps / 2d); //find the center step
            else
                centerStep = -1; //set it to -1 so it's ignored later
 
            Integer intermediateSize;
            Integer previousIntermediateSize = nImageLargestDim;
 
            for (Integer i = 0; i < numSteps; i++) {
                if (i + 1 != centerStep) {
                    //if this isn't the center step
 
                    if (i == numSteps - 1) {
                        //if this is the last step
                        //fix the stepsize to account for decimal place errors previously
                        currentStepSize = previousIntermediateSize - largestDimension;
                    }
                    else {
                        if (numSteps - i > numSteps / 2) //if we're in the first half of the reductions
                            currentStepSize = heavierStepSize;
                        else
                            currentStepSize = lighterStepSize;
                    }
                }
                else {
                    //center step, use natural step size
                    currentStepSize = stepSize;
                }
 
                intermediateSize = previousIntermediateSize - currentStepSize;
                scale = intermediateSize / (double) previousIntermediateSize;
                scaledW = Math.max((int)(scaledW * scale), 1);
                scaledH = Math.max((int)(scaledH * scale), 1);
 
                log.info("step " + i + ": scaling to " + scaledW + " x " + scaledH);
                imageOut = new BufferedImage((int) scaledW, (int) scaledH, BufferedImage.TYPE_INT_RGB); // 8 bit RGB
                g2d = imageOut.createGraphics();
                g2d.setBackground(Color.WHITE);
                g2d.clearRect(0, 0, imageOut.getWidth(), imageOut .getHeight());
                g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
                tx = new AffineTransform();
                tx.scale(scale, scale);
                g2d.drawImage(imageIn, tx, null);
                g2d.dispose();
                imageIn = new ImageIcon(imageOut).getImage();
                previousIntermediateSize = intermediateSize;
            }
        }
        else {
            // This enforces a rule that we always have an 8-bit image with white background for the thumbnail.  Plus, for large
            // images, this makes subsequent downscaling really fast because we are working on a large 8-bit image
            // instead of a large 12 or 24 bit image, so the downstream effect is very noticable.
            imageOut = new BufferedImage(imageIn.getWidth(null), imageIn.getHeight(null), BufferedImage.TYPE_INT_RGB);
            g2d = imageOut.createGraphics();
            g2d.setBackground(Color.WHITE);
            g2d.clearRect(0, 0, imageOut.getWidth(), imageOut.getHeight());
            tx = new AffineTransform();
            tx.setToIdentity(); //use identity matrix so image is copied exactly
            g2d.drawImage(imageIn, tx, null);
            g2d.dispose();
        }
 
        // JPEG-encode the image and write to file.
        saveImageAsJPEG(imageOut, fileOut);
 
        return imageOut;
    }
 
}

PDFThumbnailGeneratorImpl.java
(An empty stub impl for a PDF based thumbnail generator)

package com.raulraja.util.thumbnails.impl;
 
import com.raulraja.util.thumbnails.ThumbnailGenerator;
 
import java.io.InputStream;
import java.io.File;
import java.io.IOException;
 
/**
 * Empty impl for generating thumbnails for pdfs
 */
public class PDFThumbnailGeneratorImpl implements ThumbnailGenerator {
	/**
	 * Create the thumbnail.  The thumbnail should always save as a JPEG file.
	 *
	 * @param inputStream	  The source data.
	 * @param fileOut		  The output file.
	 * @param largestDimension The max width and height.  The generator should size the thumbnail so
	 *                         that the width and height both stay within this limit.
	 * @param hint			 Optional hint that was returned from the prior thumbnail generation
	 *                         on this same file, null if none was returned or if this is the first
	 *                         thumbnail in this context.
	 * @return an optional hint object that will be passed to subsequent thumbnail generation calls
	 *         for this same source data.  Return null if you don't use hints, otherwise return some
	 *         object which allows you to communicate extra information to the next round, such as
	 *         the scaled image already loaded.
	 */
	public Object createThumbnail(InputStream inputStream, File fileOut, int largestDimension, Object hint) throws IOException {
		throw new UnsupportedOperationException("thumbnails for pdf's not implemented in this tutorial...");
	}
}

FileUploadService.java

(The service that demonstrates how the ThumbnailGeneratorEngine can be injected as interface and used to handle streams and content types for thumbnails to be generated without actually knowing the content type or stream type. This is here for demo purposes but you can inject the engine into any other service not based on file uploads)

package com.raulraja.util.upload;
 
import com.raulraja.util.thumbnails.ThumbnailGeneratorEngine;
import org.apache.log4j.Logger;
import org.springframework.validation.BindException;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.ByteArrayMultipartFileEditor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
 
/**
 * File upload service to demonstrate files uploads and handing over the files to the thumbnail generation system
 */
public class FileUploadService extends SimpleFormController {
 
	private final static Logger log = Logger.getLogger(FileUploadService.class);
 
	private ThumbnailGeneratorEngine thumbnailGeneratorEngine;
 
	public void setThumbnailGeneratorEngine(ThumbnailGeneratorEngine thumbnailGeneratorEngine) {
		this.thumbnailGeneratorEngine = thumbnailGeneratorEngine;
	}
 
	protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws Exception {
		FileUpload fileUpload = (FileUpload) command;
		MultipartFile file = fileUpload.getFile();
		InputStream stream = file.getInputStream();
		try {
			//generate thumbnails
			thumbnailGeneratorEngine.generateThumbnails(file.getName(), stream, file.getContentType());
		} catch (Exception e) {
			log.error(e);
		} finally {
			stream.close();
		}
		return super.onSubmit(request, response, command, errors);
	}
 
	protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder)
			throws ServletException {
		// to actually be able to convert Multipart instance to byte[]
		// we have to register a custom editor
		binder.registerCustomEditor(byte[].class, new ByteArrayMultipartFileEditor());
		// now Spring knows how to handle multipart object and convert them
	}
 
}

FileUpload.java

(A simple bean that encapsulates the File reference at the time of uploading a file)

package com.raulraja.util.upload;
 
import org.springframework.web.multipart.MultipartFile;
 
/**
 * A file upload object
 */
public class FileUpload {
 
	private MultipartFile file;
 
	public MultipartFile getFile() {
		return file;
	}
 
	public void setFile(MultipartFile file) {
		this.file = file;
	}
 
}
Share and Enjoy:
  • Print
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • LinkedIn
  • MySpace
  • RSS
  • TwitThis
  • email
  • FriendFeed
  • PDF
  • Slashdot

This post is tagged , , , ,

don't '

2 Responses

  1. [...] no real functionality I have used this same pattern to create services that generate thumbnails, process images, extract text from files based on content types, [...]

  2. sefler says:

    Very Good !!

Leave a Reply

Comments may be held for moderation, please do not repost. I reserve the right to remove any inappropriate or off-topic comments. If you plan on sharing helpful code, please pass it through Postable first. Want other to know who you are? Register a Gravatar.





Sponsored Links

Categories

Pages

glactic siv ii dark gamespot
huraikan kebaikan mempelajari seni dalam pendidikan
12 cell embryo transfer
apple morphine
driving nextel tractor trailers
at t gprs settings p168
himalayan cat history
baby sister birth announcement
bausch lomb metallograph
adapter hose bib to air compressor
download marvel vs capcom dreamcast
rodger cravens
exercises to relieve back pain
cha behavioral health care sympsium
2pac edited
entourage ari job interview
xushobby.com
carbohydrate snacks for diabetics
prevent scarring with
canadian costal vacation cottages
patientsafetyfirst.org
automotive ic
chris brow run it
finned pack exchangers
reflexive intuition
darla godfrey motorcycle accident
1989 isuzu trooper parts
healthcare comany boca ration florida
2 pac feat biggie running
628 manitou rd se calgary ab
corvette nos parts
edwin wiley grove
build with legos
christianity framework
low morale in the wok place
room111heroes.com
raroc risk adjusted return on capital
definitions of negotiation
bobby ogden portland oregon
after death of jesus what happen
100 proof
bashlin lineman gaffs
affiliatetraction.com
adjustable pistol sight eaa witness compact
county inn suites mankato
alf episodes
lakehopestatepark.com
3ds importer for maya
darrell hensley
paragon ballroom roselle park nj
848 ardis vaughn
hirewelders.com
big pond otis
thumbspower.com
jsp advantage disadvantage
babes in tight shorts gallery
326 e main st ravenna
burgundytoday.com
7 dollar scripts
nightmare before christmas silhouettes
anthropologie promotions
coffee cupcake recipes
925e.com
a w richard sipe
european parliament empty seat 666
blueridge slots
executive director of philanthropy salary
catholic doctrine the sabbath
integrating literacy urban school
myflooring.info
corn starch extra fine corn mill
dickie dee hotdog lemonade fruit cart
edna cox
24 hr frys near 85042
sir john whitmore
phsource.us
anthony dillie midland tx
cum-eating-whores.com
1999 san antonio spurs
blazing ink tattoo shop
immanuel hospital omaha ne
arundel mills mall hanover maryland
berkeley advisors horvath
air pollution from poultry farms
cardboard display pos
canadian tire assistant vice president
tubularspices.com
russian authors views of color revolutions
amazon ca hans neleman books
code of silence
therepublicannews.com
all brands of bourbon
1957 hillman husky windshield
milestone distributors
pantera.com
autobahn melville
grosfillex fidji highback stacking
hot skeet
girls with donkeys
average persons iq
akira ghost in shell
aerial imagery free
firearms disassemble sks chinese
obstacle detection advantages disadvantages
calphalon enamel cast iron
hotdog chilli recipes
different strokes theme lyrics
lhmopars.com
airworks zephyr
bounty hunter medal detectors
clarion radio pinout
bitter tea imeem lollipop
1031 al exchange
aguilera aint no other man
annabel lee edgar allan poe analysis
apple varieties for cooking and eating
cannot search properly on google
lucy and ricky ricardo costumes
bill frazier
shana bowers
1965 ignition switch diagram
roushhonda.com
lyrics in english ricardo arjona
birth control pills and blood clots
redrival.com
ancestors of leslie tilbrook born 1902
2009 craddock forces nato
abcteachingjobs.com
1952 buick exhaust manifold
foxpro odbc
pete wentz arm
articles by linda darling hammond
138kv breaker
forster tuncurry accommodation
developed lakes and communities
alice walkr and ernest gaines
new idea uni sytem parts
justoffbase.co.uk
business case framework
.30-30 reloading data red dot
16 gage earrings
porn-mature.org
100 most useful gifts for women
2008 xterra
blue point siamese male
mc-ala.org
average dose of klonopin
atom center alaska
clifford macgregor
ccna 1 v4.0
acc 25 265
cvs 41 midway
autoimmune disease in german shepherds
stormyleather.com
how to suffer
dll files quicken
1990 mako boat
disater preparedness
boat countertop
11043 oroville hwy
dads doing there sons
animate your own 3d picture online
starry night by van gogh tile
kpmg glasgow vacancies
2004 buick rainier front tow hook
474 interstate replaces
zodiacs4u.com
baby boy christening gown
back to the future star christopher
freehdporn.net
bromelain and arterial plaque
millville pa notary public
wareztoplist.com
hyper-v vm disappear
afi teak
college footbal coach changes
bikram london west
mediaonecars.com
2007 crystal skulls update
esau milo
ancient cities in the jungle
bishop connolly
600 amps general electric ak-2a-25
1970 arctic cat panther
bon jovi only lonely
address for jennifer dean gainsville florida
gunit.com
2007 irish golf open winner
106.3 radio chatham
beautiful baby beautiful hbo elliott erwitt
cellulosic pulp
bluetick hound pups for sale
fram ancestry
ole time lyrics happy wanderer
america as a religious refugee
bette midler las vegas show
deliciousdays.com
gunz injector
currently filming on location downey ca
arlington county va register of deeds
1035 fm halifax
a bed breakfast at wisteria way
shs.net
1970 s cove heaters
artists similar to gabriella cilmi
carlos gomez slam
acdc photos for my profile
1986 honda spree piston rings
175 walnut ave santa cruz
chameleon canoes
calenders kids printable
generated credit cards
devastating earthquakes along convergent plate boundaries
bmxultra.com
angola air airway
jeffrey koontz
cell phone dealers in visalia ca
bording schools for 7th graders
1996 subaru montana
arched wrought iron lawn edging
810whb.com
70 s symbols
asc coupler
car rental in tenerife grand canaria
paintballbuster.com
choosing a computer
california houseboat rules
chanel handbag knock off
are wireless routers as good
compile macros
targus
firstsavingsbanks.com
bull shoals arkansas scuba
all freezer 14 cub
activision keygen
1950 s tool cataloug
170 80-15 spokes motorcycle tires
free shredding and bank
adalib.org
chamillionaire king knog
black tailed virus
1970 women tennis players
mujeresdesnudas.com.es
man into hawk tf
cheats for godzilla destroy all
2005 arctic cat zr 900
8 straight stem tire tubes
softwarefeast.com
algonquin flags and symbols
clear kerosene suppliers in new jersey
gisele sass
amc movie theater in humble
buttaro pottstown
sexinvideos.com
2005 nissan frontier nismo production
casted memories
20 coupon linens n things
pollenwidgets.com
cane creek headsets
all shoes with vibram soles
thecontractsite.net
8 line poems
100 free tax filing
60 mph electric motor bikes
missohiousa.com
colour tv in all rooms scarborough
corral cleaning
gainsville classified
xamps.com
horn microphones
camelot castle history
300 march to glory game download
bloke sportbike forum
average annual dew point in maine
815 west hastings
anh joseph gao
1970 buick gran sport
cheesy hash brown casserole
wakeboarding parks
fonda self
free online chevrolet service manuels
alison braun
bland kinny
britney skye pool
interior doors watford uk
cheatinfo.de
dumb and dumber suit
8.00 puerto rico tickets
because to see her lips they
dbx 386
buy raspberry pie
one-way anova with unequal sample sizes
deepest point 407 meters
dragonball-z.us
frequently asked questions about gerund
altoids licorice
macombsheriff.com
fictional children books on the holocaust
bikinis for your body type
househacker.com
archvision rpc resort free
25545 fansites
harlequin books download
lvl rafters
1993 sun tracker party barge 24
batch rename rotate mac freeware
coded welder definition
church choral music songbook
antonia feat sandra
advance reader copies hyperion teens
buddha monk
carstuckgirls.com
2005 tacoma glove compartment shock
andrew mitchell elementary
bc1.com
3d models of osmosis
air sport internationale sa geneva swiss
glens falls frive in
flogging molly salty dog
02 accord rims
guitar tanglewood tw15
airline industry s unemployment rate chart
buy a cat bolingbrook chanel 6
department of tourism government of karnataka
2005 m nchhof estate riesling
anclote river salinity
curt cattau
earthquakesound.com
2002 suzuki marauder vz800
fats and oils nutrition lesson plan
camshaft 350 chevy
2220 west chew street allentown pa
arik airline job vacancy
1403 income taxes
concrete stamping techniques
carver reciever
adaptations plants reduce water loss
2006 stanley cup playoffs
canine conception lh progesterone
gama seal lids
2113 garage door opener
adobe executive
1998 gm 14 bolt 6 lug
bobby whitaker plainview
2008 suzuki bandit gt
408a retirement account
accounting majors belleville il
create a headstone
chrono cross ntsc