/**
*
*/
package com.aurifa.struts2.plugin.image.validator;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.opensymphony.xwork2.validator.ValidationException;
import com.opensymphony.xwork2.validator.validators.FieldValidatorSupport;
/**
* <p>
* <!-- START SNIPPET: javadoc --> This is an image validator. You can use this
* to validate images by passing urls or uploaded files. For now, you can
* specify:
* <ul>
* <li>min/max dimensions</li>
* <li>the mimetype (JPEG, GIF, BMP, PCX, PNG, IFF, RAS, PBM, PGM, PPM and PSD
* formats are supported)</li>
* <li>max filesize in bytes</li>
* <li>if uploads and/or remote images (specified by url) are allowed</li>
* </ul>
* This class uses the excellent ImageInfo (written by Marco Schmidt), which
* allows us to validate the image without fully loading it (when using urls).
* <!-- END SNIPPET: javadoc -->
* </p>
* <p>
* Possible parameters for this validator:
*
* <!-- START SNIPPET: parameters -->
* <ul>
* <li>maxWidth - in pixels</li>
* <li>minWidth - in pixels</li>
* <li>maxHeight - in pixels</li>
* <li>minHeight - in pixels</li>
* <li>maxSize - in bytes</li>
* <li>remote - boolean (default false)</li>
* <li>upload - boolean (default false)</li>
* <li>allowedMimeTypes - comma seperated list of allowed mime types</li>
* </ul>
* <!-- END SNIPPET: parameters -->
* </p>
* <p>
* <!-- START SNIPPET: example --> Here's an example to validate an avatar for a
* standard webbased forum:
*
* <pre>
* <field name="avatar">
* <field-validator type="image">
* <param name="maxWidth">80</param>
* <param name="maxHeight">80</param>
* <param name="minWidth">40</param>
* <param name="minHeight">40</param>
* <param name="maxSize">40000</param>
* <param name="remote">false</param> <!--only uploads allowed !-->
* <param name="allowedMimeTypes">image/gif,image/jpg,image/png</param>
* <message key="uploaded.avatar" />
* </field-validator>
* </field>
* </pre>
*
* </p>
* <!-- END SNIPPET: example --> <!-- START SNIPPET: error -->
* <p>
* The keys you should be using for your localised error reporting are always
* made of the original key you provided in the validator + the possible error.
* So, if you have a key named 'uploaded.avatar' (=key_name), and the image
* would be too wide, you would get a message key back named
* 'uploaded.avatar.invalid.width.max'
*
* The other keys:
* <ul>
* <li>key_name + 'invalid': the provided image was not valid</li>
* <li>key_name + 'invalid.location': the remote image location should start with http://</li>
* <li>key_name + 'invalid.notallowed': the requested action (remote or fileupload) is not allowed</li>
* <li>key_name + 'invalid.notfound': the image could not be located</li>
* <li>key_name + 'invalid.mime': the mimetype of the image was not listed in
* the allowedMimeTypes parameter</li>
* <li>key_name + 'invalid.width.min': the image's width didn't meet the
* required minimal width in pixels</li>
* <li>key_name + 'invalid.width.max': the image's width didn't meet the
* required maximal width in pixels</li>
* <li>key_name + 'invalid.height.min': the image's height didn't meet the
* required minimal height in pixels</li>
* <li>key_name + 'invalid.height.max': the image's height didn't meet the
* required maximal height in pixels</li>
* <li>key_name + 'invalid.size': the image file size was too big</li>
*
* </ul>
* <!-- END SNIPPET: error -->
* </p>
*
* @author <a href="mailto:philip.luppens@gmail.com">Philip Luppens</a>
* @version 1.0
*/
public class CombinedImageValidator extends FieldValidatorSupport {
private final static Log logger = LogFactory
.getLog(CombinedImageValidator.class);
private Integer maxWidth, maxHeight;
private Integer minWidth, minHeight;
private Long maxSize;
private int imageWidth, imageHeight, imageSize;
private String imageMimeType;
private boolean remote, upload = false;
private List allowed;
private ImageInfo ii;
// tokens of mimetypes, seperated by commas
private String allowedMimeTypes;
/*
* (non-Javadoc)
*
* @see com.opensymphony.xwork.validator.Validator#validate(java.lang.Object)
*/
public void validate(Object object) throws ValidationException {
allowed = tokenizeMimeTypes();
String fieldName = getFieldName();
Object fieldValue = getFieldValue(fieldName, object);
if (fieldValue != null) {
Object[] images;
if (fieldValue.getClass().isArray()) {
images = (Object[]) fieldValue;
} else {
images = new Object[] { fieldValue };
}
for (int i = 0; i < images.length; i++) {
if (logger.isDebugEnabled()) {
logger.debug("Image: " + images[i]);
}
}
for (int i = 0; i < images.length; i++) {
ii = new ImageInfo();
if (logger.isDebugEnabled()) {
logger.debug("Validating Image: " + images[i]);
}
if (!isValid(images[i])) {
addFieldError(fieldName, object);
return;
}
}
} else {
logger.warn("Field value not found");
addFieldError(fieldName, object);
return;
}
}
/*
* Simply tokenize the allowedMimeTypes String from our validator
* parameters.
*
*/
private final List tokenizeMimeTypes() {
// make sure we don't get a npe
if (allowedMimeTypes == null) {
return new ArrayList();
}
StringTokenizer st = new StringTokenizer(allowedMimeTypes, ",");
List<String> types = new ArrayList<String>();
while (st.hasMoreTokens()) {
types.add((st.nextToken()).trim());
}
return types;
}
/**
* This method will try to validate an image Object. Be aware that this
* could mean it is a String with the location or an uploaded File.
*
* @param image
* @return
* @throws ValidationException
*/
public boolean isValid(Object image) throws ValidationException {
if (image instanceof String) {
if (!remote){
//we're not allowed to pass remote images
setMessageKey(getMessageKey() + ".invalid.notallowed");
// no need to continue
return false;
}
// ah, this is a remote file location
if (((String) image).toLowerCase().startsWith("http://")) {
URL url = null;
try {
url = new URL((String) image);
// set up our image info stream
ii.setInput(url.openStream());
} catch (Exception e) {
logger.error(e);
setMessageKey(getMessageKey() + ".invalid.notfound");
// no need to continue
return false;
}
// image's filesize is checked by requesting the content
// length header
try {
imageSize = url.openConnection().getContentLength();
} catch (Exception e) {
logger.error(e);
// could not check the content length ?
setMessageKey(getMessageKey() + ".invalid.size");
// no need to continue
return false;
}
} else {
//non-remote urls are not accepted
setMessageKey(getMessageKey() + ".invalid.location");
return false;
}
} else if (image instanceof File) {
if (!upload){
//we're not allowed to upload images
setMessageKey(getMessageKey() + ".invalid.notallowed");
// no need to continue
return false;
}
// file upload
File i = (File) image;
try {
ii.setInput(new FileInputStream(i));
} catch (FileNotFoundException e) {
// could not find the file ? Highly unlikely, but it *could*
// happen
// This is a critical exception, so we better throw a
// ValidationException
// no need to continue
logger.error(e);
throw new ValidationException("Could not open inputstream for "
+ i);
}
imageSize = (int) i.length();
} else {
// it's .. uhm, something else. A football ? A hot blonde's address
// ? Fourty Two ? We'll never know ..
throw new ValidationException(
"Invalid object. Expect either a String or File object. Object is "
+ image.getClass().getName());
}
if (!ii.check()) {
setMessageKey(getMessageKey() + ".invalid");
return false;
}
// check if the mimetype is allowed
imageMimeType = ii.getMimeType();
if (!allowed.contains(imageMimeType)) {
if (logger.isDebugEnabled()) {
logger.debug("Invalid mimetype: " + ii.getMimeType());
}
setMessageKey(getMessageKey() + ".invalid.mime");
return false;
}
imageWidth = ii.getWidth();
imageHeight = ii.getHeight();
// not entirely sure about the best error reporting way ..
if (maxWidth != null && ii.getWidth() > maxWidth.intValue()) {
if (logger.isDebugEnabled()) {
logger.debug("Invalid width: " + ii.getWidth());
}
setMessageKey(getMessageKey() + ".invalid.width.max");
return false;
}
if (minWidth != null && ii.getWidth() < minWidth.intValue()) {
if (logger.isDebugEnabled()) {
logger.debug("Invalid width: " + ii.getWidth());
}
setMessageKey(getMessageKey() + ".invalid.width.min");
return false;
}
if (maxHeight != null && ii.getHeight() > maxHeight.intValue()) {
if (logger.isDebugEnabled()) {
logger.debug("Invalid height: " + ii.getHeight());
}
setMessageKey(getMessageKey() + ".invalid.height.max");
return false;
}
if (minHeight != null && ii.getHeight() < minHeight.intValue()) {
if (logger.isDebugEnabled()) {
logger.debug("Invalid height: " + ii.getHeight());
}
setMessageKey(getMessageKey() + ".invalid.height.min");
return false;
}
if (maxSize != null && imageSize > maxSize.longValue()) {
if (logger.isDebugEnabled()) {
logger.debug("Invalid size: " + imageSize);
}
setMessageKey(getMessageKey() + ".invalid.size");
return false;
}
return true;
}
public Integer getMaxHeight() {
return maxHeight;
}
public void setMaxHeight(Integer maxHeight) {
this.maxHeight = maxHeight;
}
public Long getMaxSize() {
return maxSize;
}
public void setMaxSize(Long maxSize) {
this.maxSize = maxSize;
}
public Integer getMaxWidth() {
return maxWidth;
}
public void setMaxWidth(Integer maxWidth) {
this.maxWidth = maxWidth;
}
public String getAllowedMimeTypes() {
return allowedMimeTypes;
}
public void setAllowedMimeTypes(String allowedMimeTypes) {
this.allowedMimeTypes = allowedMimeTypes;
}
// image properties after processing
public int getImageHeight() {
return imageHeight;
}
public String getImageMimeType() {
return imageMimeType;
}
public int getImageSize() {
return imageSize;
}
public int getImageWidth() {
return imageWidth;
}
public Integer getMinHeight() {
return minHeight;
}
public void setMinHeight(Integer minHeight) {
this.minHeight = minHeight;
}
public Integer getMinWidth() {
return minWidth;
}
public void setMinWidth(Integer minWidth) {
this.minWidth = minWidth;
}
/**
* @return Returns the remote.
*/
public boolean isRemote() {
return remote;
}
/**
* @param remote The remote to set.
*/
public void setRemote(boolean remote) {
this.remote = remote;
}
/**
* @return Returns the upload.
*/
public boolean isUpload() {
return upload;
}
/**
* @param upload The upload to set.
*/
public void setUpload(boolean upload) {
this.upload = upload;
}
}