How to create a constraint for image size upload

Question

How to add a constraint on a specific content type for an image?

Answer

You can define a custom validator. E.g. a validator for the image size:

In a custom module you can define a custom validator for any field:

When you have a custom definition of a type with an image field like:


[jnt:imageValTest] > jnt:content, jmix:editorialContent, jmix:structuredContent
 - header (string)
 - myImage (weakreference, picker[type='image']) 

And you want to limit the upload size (selectable image size) for the myImage field, you have to define a constraint (in case of FileSize no default constraint exists):


package org.foo.modules.validators;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

@Target({ METHOD, FIELD })
@Retention(RUNTIME)
@Constraint(validatedBy = FileSizeValidator.class)
@Documented
public @interface FileSize {
    Class[] groups() default {};

    long max();

    String message() default "{javax.validation.constraints.file.size}";
    
    long min();

    Class[] payload() default {};
}

Note: Inside this Interface you have to define the error message, in the example it has the key

javax.validation.constraints.file.size.

Also define needed methods, in this case a min and max file size.

In the header you have to define a Validator class, where you can add the correct checks:


package org.foo.modules.validators;

import javax.jcr.RepositoryException;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import org.jahia.services.content.JCRNodeWrapper;

public class FileSizeValidator implements ConstraintValidator {

    private long max;
    private long min;

    @Override
    public void initialize(FileSize constraintAnnotation) {
        this.min = constraintAnnotation.min();
        this.max = constraintAnnotation.max();
    }
  

	@Override
	public boolean isValid(Object val, ConstraintValidatorContext arg1) {
		MyImageValidator validator = null;
		if(val != null && val instanceof MyImageValidator) {
			validator = (MyImageValidator)val;
		} else {
			return false;
		}
        try {
            JCRNodeWrapper imageNode = validator.getImageNode();
            if (imageNode == null) { //if imageNode is null return false because no file is selected (return true if you allow empty field)
            	return false;
            }
            long contentLength = imageNode.getFileContent().getContentLength();

            return (min <= 0 || contentLength >= min) && (max <= 0 || contentLength <= max);

        } catch (RepositoryException e) {
            // ...
        }
        return false;
	}
}

In the next step, you need a JCRNodeValidator, for that define a class where you return the correct field which is used by the validator above:


package org.foo.modules.validators;

import javax.jcr.ItemNotFoundException;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.ValueFormatException;

import org.jahia.services.content.JCRNodeWrapper;
import org.jahia.services.content.decorator.validation.JCRNodeValidator;

public class MyImageValidator implements JCRNodeValidator {

	private JCRNodeWrapper node;

	private JCRNodeWrapper imageNode;
	

	public MyImageValidator(JCRNodeWrapper node) {
		super();
		this.node = node;
	}

	JCRNodeWrapper getImageNode()
			throws ItemNotFoundException, ValueFormatException, PathNotFoundException, RepositoryException {
		if (imageNode == null && node.getProperty("myImage") != null) {
			imageNode = (JCRNodeWrapper) node.getProperty("myImage").getNode();
		}
		return imageNode;
	}

	@FileSize(min = 0, max = 1048576L)
	public MyImageValidator getMyImage() {

		return this;

	}

}

Here you can use the defined constraint above @FileSize(min = 0, max = 1048576L) to define the correct limits.

And least you have to put the Validator in place for the correct content type. To do it, you have to define a JCRNodeValidatorDefinition bean (can be registered with OSGI):


package org.foo.modules.validators;

import java.util.Collections;
import java.util.Map;

import org.jahia.services.content.decorator.validation.JCRNodeValidatorDefinition;
import org.osgi.service.component.annotations.Component;

@Component(service = JCRNodeValidatorDefinition.class)
public class MyCustomValidators  extends JCRNodeValidatorDefinition { 
	
	   /**
     * Register custom validator for generic content nodetype
     *
     * @return validators list
     * @see org.foo.modules.validators.MyImageValidator
     */
    @Override
    public Map getValidators() {
        return Collections.singletonMap("jnt:imageValTest", MyImageValidator.class);
    }

}

Now, the module can be compiled. And the Validator will be in place for the jnt:imageValTest node type. 

 

Another full example can be shown on GitHub issue, see the attached file