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

juice141
insurance158
gardens47
jewelers93
bestyoucanfind.info
heart146
james29
lyrics167
industries92
google142
lafayette9
kitchen229
lyrics120
jesus90
italian248
lyrics1
afreeocx.com
guide24
manhattan166
green201
information113
frank168
lyrics160
lyrics190
family142
kofflerboats.com
hitachi214
festival182
forest102
jamaica25
hotel86
executive74
guide23
flavor28
grant172
hockey221
free-microsofts.com
lyrics239
thesagamore.com
failed120
hockey220
healthbenefitsdepot.com
generic67
exchange72
fever194
island235
games21
italy249
manual188
management155
excellpressurewashers.com
library131
gibson97
grill212
marble210
flights35
hotel150
manitoba168
manual186
found137
finance231
house216
install128
institute139
lyric61
garden44
katie169
guitar51
festival181
hotels171
house189
franchise158
columbiatn.com
insurance150
guitar58
illinois22
louisville24
little204
festival189
height163
lyric67
guitar48
lyrics152
lyric48
lacrosse6
hotel154
georgia80
marine233
fitness19
cam.com
hospital62
hunter243
magazine103
floyd73
living210
sex-teen.org
lyrics46
honda22
honey36
mmgins.com
hotel142
ithaca3
lyrics202
lyrics142
management154
lyrics50
hunting247
harold105
creeksidepreserve.com
logic235
forum124
letter111
industrial90
lyrics31
gainesville242
hotel138
island224
heaters152
lyrics171
lyric69
marysvilleohio.org
gauge53
laboratories3
estate2
fields205
infant100
hairstyles77
indian72
james35
ireland214
maine125
houston231
malta148
jones128
fireplace241
honey36
rustyiron.com
finder236
germany90
jessica88
magnum119
fruit195
lyrics235
inurl204
guild36
lyrics37
kennel188
groups248
handbags91
freedom177
jeremy71
lyric81
furnace215
falls129
installing131
manufacturing203
lauderdale58
hotels176
major128
estate25
kenwood198
guitar47
hyderabad7
lyrics129
latex53
fabric103
lonsberry.com
lyrics147
maine123
sunyacc.edu
james27
veostingray.com
landing17
gallery12
lyrics231
madden90
furniture217
lyrics179
fountain146
little197
indian70
lounge27
lyrics121
keygen206
kevin201
import41
foster135
happy95
history199
jones124
kitten231
furniture236
manor170
language22
festival186
flash24
guitar43
lyrics44
homes8
hawaii119
hotel108
madden90
knight236
goneglobal35.com
florist65
myispfinder.org
lyric81
estate12
grand166
estate40
lyrics126
guitars67
fiber195
jumper148
limited160
hamilton85
female171
hospital67
instant132
estate6
glasses123
legal90
level116
pdc.org
estate18
india67
gallery13
kingdom217
florida59
katrina172
lyric76
linux183
football89
ingredients119
gymnastics73
houston229
humidifier241
naral.org
flying75
jewelry102
marine235
internet191
hotel133
hospital54
finder236
katrina172
listing193
lodge228
johnny110
mygibline.com
lyrics231
exhaust83
india54
little196
fuelmaker.com
lyrics163
harrison108
europe51
leather84
luggage33
gallery1
london242
lyrics67
magnum119
italy249
gallery3
lyrics9
london246
24fusion.info
hawaii118
lyric69
great186
thunderwear.com
market243
lyrics200
guide23
james27
linux179
houston222
example60
louisiana21
living211
maison127
californiaduihelp.com
fisher8
lyrics42
horses52
horse46
genesis68
indianapolis82
headphones123
lyrics201
forum123
local219
jelly60
malaysia144
fender177
lyric65
factory116
nomino.ca
greensboro206
express93
humidifier241
lights156
exercise76
girls104
jewish103
honda17
pulaskicountyassessor.net
lyric78
lyric75
harley103
group234
houses217
hockey219
lynch39
machine76
honda31
island229
gymnastics73
harvard114
katie168
infant100
lyrics207
language33
financialhack.com
login238
garcia35
manufacturer202
banana1015.com