/*
* $Id: ImageProcessor.java,v 1.8.2.1 2007/01/12 19:32:54 idegaweb Exp $ Created on
* Sep 30, 2004
*
* Copyright (C) 2004 Idega Software hf. All Rights Reserved.
*
* This software is the proprietary information of Idega hf. Use is subject to
* license terms.
*/
package com.idega.block.image.business;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.ejb.CreateException;
import com.idega.block.image.data.ImageEntity;
import com.idega.block.image.data.ImageProcessJob;
import com.idega.business.IBOLookup;
import com.idega.idegaweb.IWApplicationContext;
import com.idega.idegaweb.IWCacheManager;
import com.idega.idegaweb.IWMainApplication;
import com.idega.presentation.IWContext;
import com.idega.util.FileUtil;
import com.idega.util.caching.Cache;
/**
*
* Last modified: $Date: 2007/01/12 19:32:54 $ by $Author: idegaweb $
*
*
* @author <a href="mailto:eiki@idega.com">eiki </a>
* @version $Revision: 1.8.2.1 $
*/
public class ImageProcessor implements Runnable {
/** Folder where the modified images are stored */
public static final String MODIFIED_IMAGES_FOLDER = "modified_images";
static ImageProcessor imageProcessor;
static final String STORAGE_KEY = "ImageProcessorInstance";
boolean runThread = true;
Thread thread;
private boolean isRunning = false;
private Map unprocessedImages = new HashMap();
private List inProcessOrDone = new ArrayList();
private IWApplicationContext iwac;
/**
*
*/
private ImageProcessor(IWApplicationContext iwac) {
super();
this.iwac = iwac;
}
public static ImageProcessor getInstance(IWApplicationContext iwac) {
imageProcessor = (ImageProcessor) iwac.getApplicationAttribute(STORAGE_KEY);
if (imageProcessor == null) {
imageProcessor = new ImageProcessor(iwac);
iwac.setApplicationAttribute(STORAGE_KEY, imageProcessor);
}
return imageProcessor;
}
/*
* (non-Javadoc)
*
* @see java.lang.Runnable#run()
*/
public void run() {
this.isRunning = true;
try {
while (this.runThread && !this.unprocessedImages.isEmpty()) {
processImages();
}
}
catch (Exception e) {
if (this.runThread) {
this.runThread = false;
e.printStackTrace();
}
}
this.isRunning = false;
}
/**
* Does the actual image processing and saves the images to the database
*/
private void processImages() {
Object[] jobs = this.unprocessedImages.values().toArray();
for (int i = 0; i < jobs.length; i++) {
ImageProcessJob job = (ImageProcessJob) jobs[i];
//put it in the "busy" list
this.inProcessOrDone.add(job.getJobKey());
try {
processImage(job);
}
catch (Exception e) {
e.printStackTrace();
}
//we are done with it wether it worked or not
this.unprocessedImages.remove(job.getJobKey());
}
}
/**
* Scales the image to its new dimensions
*
* @param job
* @param iwc
* @throws IOException
* @throws RemoteException
* @throws CreateException
*/
private void processImage(ImageProcessJob job) throws IOException, RemoteException, CreateException {
// get image encoder
ImageEncoder imageEncoder = getImageEncoder();
// get all the job data
Cache cachedImage = job.getCachedImage();
String realPathToImage = cachedImage.getRealPathToFile();
ImageEntity imageEntity = (ImageEntity) cachedImage.getEntity();
String mimeType = imageEntity.getMimeType();
String imageName = imageEntity.getEntityName();
String originalImageID = imageEntity.getPrimaryKey().toString();
int widthOfModifiedImage = job.getNewWidth();
int heightOfModifiedImage = job.getNewHeight();
String extension = job.getNewExtension();
String nameOfModifiedImage = job.getJobKey();
String pathOfModifiedImage = getRealPathOfModifiedImage(widthOfModifiedImage, heightOfModifiedImage, extension,
imageName, originalImageID);
// now create the new image...
// get input from the cached file instead of from the database because
// get imageEntity.getFileValue() causes End-Of-File Exception when JAI
// tries to read the file fully
FileInputStream input = new FileInputStream(realPathToImage);
// get output
OutputStream output = new FileOutputStream(pathOfModifiedImage);
// encode the image
try {
imageEncoder.encode(mimeType, input, output, widthOfModifiedImage, heightOfModifiedImage);
}
catch (Exception ex) {
// delete the created file (you can not use the result)
output.close();
input.close();
(new File(pathOfModifiedImage)).delete();
ex.printStackTrace();
}
finally {
output.close();
input.close();
}
//Save the image to the database
FileInputStream inputStream = new FileInputStream(pathOfModifiedImage);
ImageEntity motherImage = imageEntity;
ImageProvider imageProvider = getImageProvider();
imageProvider.uploadImage(inputStream, mimeType, nameOfModifiedImage,
widthOfModifiedImage, heightOfModifiedImage, motherImage);
inputStream.close();
}
private String getRealPathOfModifiedImage(int width, int height, String extension, String imageName,
String originalImageID) {
String separator = FileUtil.getFileSeparator();
StringBuffer path = new StringBuffer(this.iwac.getIWMainApplication().getApplicationRealPath());
path.append(IWCacheManager.IW_ROOT_CACHE_DIRECTORY).append(separator).append(MODIFIED_IMAGES_FOLDER);
// check if the folder exists create it if necessary
// usually the folder should be already be there.
// the folder is never deleted by this class
FileUtil.createFolder(path.toString());
path.append(separator).append(
getNameOfModifiedImageWithExtension(width, height, extension, imageName, originalImageID));
return path.toString();
}
private String getNameOfModifiedImageWithExtension(int width, int height, String extension, String imageName,
String originalImageID) {
int pointPosition = imageName.lastIndexOf('.');
int length = imageName.length();
// cut extension (imageName.a imageName.ab imageName.abc but not
// imageName.abcd)
if ((pointPosition > 0) && pointPosition > (length - 5)) {
imageName = imageName.substring(0, pointPosition);
}
StringBuffer nameOfImage = new StringBuffer();
// add new extension
nameOfImage.append(originalImageID);
nameOfImage.append(width).append("_").append(height).append("_").append(imageName).append(".").append(extension);
return nameOfImage.toString();
}
private Cache getCachedImage(IWContext iwc, int imageId) {
// this method is similar to the private getImage() method of the super
// class Image
IWMainApplication iwma = iwc.getIWMainApplication();
return IWCacheManager.getInstance(iwma).getCachedBlobObject(
com.idega.block.image.data.ImageEntity.class.getName(), imageId, iwma);
}
private String getRealPathToImageAndImageEntity(IWContext iwc, int originalImageID) {
Cache cachedImage = getCachedImage(iwc, originalImageID);
return cachedImage.getRealPathToFile();
}
private ImageEncoder getImageEncoder(IWContext iwc) throws RemoteException {
return (ImageEncoder) IBOLookup.getServiceInstance(iwc, ImageEncoder.class);
}
private ImageProvider getImageProvider(IWContext iwc) throws RemoteException {
return (ImageProvider) IBOLookup.getServiceInstance(iwc, ImageProvider.class);
}
public void start() {
this.runThread = true;
if (this.thread == null || !this.isRunning) {
//a new thread must be created here because it was null or
//we went out of the run() method. When run is finished the thread is considered dead and cannot be restarted
this.thread = new Thread(this, "ImageProcessor Thread");
//this is a backround task
this.thread.setPriority(Thread.NORM_PRIORITY);
this.thread.start();
}
}
public void stop() {
if (this.thread != null) {
this.runThread = false;
this.thread.interrupt();
}
}
/**
* You should use this method to add Image Processing jobs. It automatically
* starts the processing thread
*
* @param job
*/
public void addImageProcessJobToQueu(ImageProcessJob job) {
String key = job.getJobKey();
if (!isInProcessOrDone(key)) {
addToQueu(job);
//if the processes are not being processed already (run() not
// running) we start it now.
this.start();
}
}
private synchronized void addToQueu(ImageProcessJob job) {
this.unprocessedImages.put(job.getJobKey(), job);
}
/**
* Tells you if it is already being processed or is in the queu
*
* @param key
* @return
*/
private boolean isInProcessOrDone(String key) {
return this.unprocessedImages.containsKey(key) || this.inProcessOrDone.contains(key);
}
/** Destroy the thread */
public void destroy() {
stop();
this.thread = null;
}
private ImageEncoder getImageEncoder() throws RemoteException {
return (ImageEncoder) IBOLookup.getServiceInstance(this.iwac, ImageEncoder.class);
}
private ImageProvider getImageProvider() throws RemoteException {
return (ImageProvider) IBOLookup.getServiceInstance(this.iwac, ImageProvider.class);
}
}