All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.tools.xjc.addon.krasa.JaxbValidationsPlugins Maven / Gradle / Ivy

Go to download

JAXB plugins for generation of Bean Validation Annotations (JSR-303) and replacing primitive types

There is a newer version: 1.8
Show newest version
package com.sun.tools.xjc.addon.krasa;

import static com.sun.tools.xjc.addon.krasa.Utils.toInt;

import java.io.IOException;
import java.math.BigInteger;
import java.util.Collections;
import java.util.List;

import javax.persistence.Column;
import javax.validation.Valid;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Digits;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

import org.xml.sax.ErrorHandler;

import com.sun.codemodel.JAnnotationArrayMember;
import com.sun.codemodel.JAnnotationUse;
import com.sun.codemodel.JFieldVar;
import com.sun.tools.xjc.BadCommandLineException;
import com.sun.tools.xjc.Options;
import com.sun.tools.xjc.Plugin;
import com.sun.tools.xjc.model.CAttributePropertyInfo;
import com.sun.tools.xjc.model.CElementPropertyInfo;
import com.sun.tools.xjc.model.CPropertyInfo;
import com.sun.tools.xjc.model.CValuePropertyInfo;
import com.sun.tools.xjc.outline.ClassOutline;
import com.sun.tools.xjc.outline.FieldOutline;
import com.sun.tools.xjc.outline.Outline;
import com.sun.xml.xsom.XSComponent;
import com.sun.xml.xsom.XSElementDecl;
import com.sun.xml.xsom.XSFacet;
import com.sun.xml.xsom.XSSimpleType;
import com.sun.xml.xsom.XSTerm;
import com.sun.xml.xsom.XSType;
import com.sun.xml.xsom.impl.AttributeUseImpl;
import com.sun.xml.xsom.impl.ElementDecl;
import com.sun.xml.xsom.impl.ParticleImpl;
import com.sun.xml.xsom.impl.RestrictionSimpleTypeImpl;
import com.sun.xml.xsom.impl.parser.DelayedRef;

/**
 * big thanks to original author: cocorossello
 */
public class JaxbValidationsPlugins extends Plugin {
	
	public static final String PLUGIN_OPTION_NAME = "XJsr303Annotations";
	public static final String TARGET_NAMESPACE_PARAMETER_NAME = PLUGIN_OPTION_NAME + ":targetNamespace";
	public static final String JSR_349 = PLUGIN_OPTION_NAME + ":JSR_349";
	public static final String GENERATE_NOT_NULL_ANNOTATIONS = PLUGIN_OPTION_NAME + ":generateNotNullAnnotations";
	public static final String NOT_NULL_ANNOTATIONS_CUSTOM_MESSAGES = PLUGIN_OPTION_NAME + ":notNullAnnotationsCustomMessages";
	public static final String VERBOSE = PLUGIN_OPTION_NAME + ":verbose";
	public static final String GENERATE_JPA_ANNOTATIONS = PLUGIN_OPTION_NAME + ":jpa";

	protected String namespace = "http://jaxb.dev.java.net/plugin/code-injector";
	public String targetNamespace = null;
	public boolean jsr349 = false;
	public boolean verbose = true;
	public boolean notNullAnnotations = true;
	public boolean notNullCustomMessages;
	public boolean notNullPrefixFieldName;
	public boolean notNullPrefixClassName;
	public String notNullCustomMessage = null;
	public boolean jpaAnnotations = false;

	public String getOptionName() {
		return PLUGIN_OPTION_NAME;
	}

	@Override
	public int parseArgument(Options opt, String[] args, int i) throws BadCommandLineException, IOException {
		String arg1 = args[i];
		int consumed = 0;
		int indexOfNamespace = arg1.indexOf(TARGET_NAMESPACE_PARAMETER_NAME);
		if (indexOfNamespace > 0) {
			targetNamespace = arg1.substring(indexOfNamespace + TARGET_NAMESPACE_PARAMETER_NAME.length() + "=".length());
			consumed++;
		}

		int index = arg1.indexOf(JSR_349);
		if (index > 0) {
			jsr349 = Boolean.parseBoolean(arg1.substring(index + JSR_349.length() + "=".length()));
			consumed++;
		}

		int index_generateNotNullAnnotations = arg1.indexOf(GENERATE_NOT_NULL_ANNOTATIONS);
		if (index_generateNotNullAnnotations > 0) {
			notNullAnnotations = Boolean.parseBoolean(arg1.substring(index_generateNotNullAnnotations
					+ GENERATE_NOT_NULL_ANNOTATIONS.length() + "=".length()));
			consumed++;
		}

		int index_notNullCustomMessages = arg1.indexOf(NOT_NULL_ANNOTATIONS_CUSTOM_MESSAGES);
		if (index_notNullCustomMessages > 0) {
			String value = arg1.substring(index_notNullCustomMessages + NOT_NULL_ANNOTATIONS_CUSTOM_MESSAGES.length() + "=".length()).trim();
			notNullCustomMessages = Boolean.parseBoolean(value);
			if (!notNullCustomMessages) {
				if (value.equalsIgnoreCase("classname")) {
					notNullCustomMessages = notNullPrefixFieldName = notNullPrefixClassName = true;
				} else if (value.equalsIgnoreCase("fieldname")) {
					notNullCustomMessages = notNullPrefixFieldName = true;
				} else if (!value.isEmpty() && !value.equalsIgnoreCase("false")) {
					notNullCustomMessage = value;
				}
			}
			consumed++;
		}

		int index_verbose = arg1.indexOf(VERBOSE);
		if (index_verbose > 0) {
			verbose = Boolean.parseBoolean(arg1.substring(index_verbose
					+ VERBOSE.length() + "=".length()));
			consumed++;
		}
		int index_generateJpaAnnotations = arg1.indexOf(GENERATE_JPA_ANNOTATIONS);
		if (index_generateJpaAnnotations > 0) {
			jpaAnnotations = Boolean.parseBoolean(arg1.substring(index_generateJpaAnnotations
					+ GENERATE_JPA_ANNOTATIONS.length() + "=".length()));
			consumed++;
		}

		return consumed;
	}

	public List getCustomizationURIs() {
		return Collections.singletonList(namespace);
	}

	public boolean isCustomizationTagName(String nsUri, String localName) {
		return nsUri.equals(namespace) && localName.equals("code");
	}

	@Override
	public void onActivated(Options opts) throws BadCommandLineException {
		super.onActivated(opts);
	}

	public String getUsage() {
		return "  -XJsr303Annotations      :  inject Bean validation annotations (JSR 303); -XJsr303Annotations:targetNamespace=http://www.foo.com/bar  :      additional settings for @Valid annotation";
	}

	public boolean run(Outline model, Options opt, ErrorHandler errorHandler) {
		try {
			for (ClassOutline co : model.getClasses()) {
				List properties = co.target.getProperties();
				for (CPropertyInfo property : properties) {
					if (property instanceof CElementPropertyInfo) {
						processElement((CElementPropertyInfo) property, co, model);
					} else if (property instanceof CAttributePropertyInfo) {
						processAttribute((CAttributePropertyInfo) property, co, model);
					} else if (property instanceof CValuePropertyInfo) {
						processAttribute((CValuePropertyInfo) property, co, model);
					}
				}
			}
			return true;
		} catch (Exception e) {
			log(e);
			return false;
		}
	}


	/**
	 * XS:Element
	 */
	public void processElement(CElementPropertyInfo property, ClassOutline classOutline, Outline model) {
		XSComponent schemaComponent = property.getSchemaComponent();
		ParticleImpl particle = (ParticleImpl) schemaComponent;
		// must be reflection because of cxf-codegen
		int maxOccurs = toInt(Utils.getField("maxOccurs", particle));
		int minOccurs = toInt(Utils.getField("minOccurs", particle));
		JFieldVar field = classOutline.implClass.fields().get(propertyName(property));

		// workaround for choices
		boolean required = property.isRequired();
		if (minOccurs < 0 || minOccurs >= 1 && required) {
			if (!hasAnnotation(field, NotNull.class)) {
				processNotNull(classOutline, field);
			}
		}
		if (maxOccurs > 1) {
			if (!hasAnnotation(field, Size.class)) {
				log("@Size (" + minOccurs + "," + maxOccurs + ") " + propertyName(property)
						+ " added to class " + classOutline.implClass.name());
				field.annotate(Size.class).param("min", minOccurs).param("max", maxOccurs);
			}
		}
		if (maxOccurs == -1 && minOccurs > 0) { // maxOccurs="unbounded"
			if (!hasAnnotation(field, Size.class)) {
				log("@Size (" + minOccurs + ") " + propertyName(property) + " added to class "
						+ classOutline.implClass.name());
				field.annotate(Size.class).param("min", minOccurs);
			}
		}

		XSTerm term = particle.getTerm();
		if (term instanceof ElementDecl) {
			processElement(property, classOutline, field, (ElementDecl) term);
		} else if (term instanceof DelayedRef.Element) {
			XSElementDecl xsElementDecl = ((DelayedRef.Element) term).get();
			processElement(property, classOutline, field, (ElementDecl) xsElementDecl);
		}

	}

	private void processElement(CElementPropertyInfo property, ClassOutline clase, JFieldVar var, ElementDecl element) {
		String propertyName = propertyName(property);
		String className = clase.implClass.name();
		XSType elementType = element.getType();

		validAnnotation(elementType, var, propertyName, className);

		if (elementType instanceof XSSimpleType) {
			processType((XSSimpleType) elementType, var, propertyName, className);
		} else if (elementType.getBaseType() instanceof XSSimpleType) {
			processType((XSSimpleType) elementType.getBaseType(), var, propertyName, className);
		}
	}

	private void processNotNull(ClassOutline co, JFieldVar field) {
		if (notNullAnnotations) {
			log("@NotNull: " + field.name() + " added to class " + co.implClass.name());
			JAnnotationUse annotation = field.annotate(NotNull.class);
			if (notNullPrefixClassName) {
				annotation.param("message", String.format("%s.%s {%s.message}", co.implClass.name(), field.name(), NotNull.class.getName()));
			} else if (notNullPrefixFieldName) {
				annotation.param("message", String.format("%s {%s.message}", field.name(), NotNull.class.getName()));
			} else if (notNullCustomMessages) {
				annotation.param("message", String.format("{%s.message}", NotNull.class.getName()));
			} else if (notNullCustomMessage != null) {
				annotation.param("message", notNullCustomMessage.replace("{ClassName}", co.implClass.name()).replace("{FieldName}", field.name()));
			}
		}
	}

	private void validAnnotation(final XSType elementType, JFieldVar var, final String propertyName,
								 final String className) {
		if ((targetNamespace == null || elementType.getTargetNamespace().startsWith(targetNamespace)) && elementType.isComplexType()) {
			if (!hasAnnotation(var, Valid.class)) {
				log("@Valid: " + propertyName + " added to class " + className);
				var.annotate(Valid.class);
			}
		}
	}

	public void processType(XSSimpleType simpleType, JFieldVar field, String propertyName, String className) {
		if (!hasAnnotation(field, Size.class) && isSizeAnnotationApplicable(field)) {
			Integer maxLength = simpleType.getFacet("maxLength") == null ? null : Utils.parseInt(simpleType.getFacet(
					"maxLength").getValue().value);
			Integer minLength = simpleType.getFacet("minLength") == null ? null : Utils.parseInt(simpleType.getFacet(
					"minLength").getValue().value);
			Integer length = simpleType.getFacet("length") == null ? null : Utils.parseInt(simpleType.getFacet(
					"length").getValue().value);

			if (maxLength != null && minLength != null) {
				log("@Size(" + minLength + "," + maxLength + "): " + propertyName + " added to class "
						+ className);
				field.annotate(Size.class).param("min", minLength).param("max", maxLength);
			} else if (minLength != null) {
				log("@Size(" + minLength + ", null): " + propertyName + " added to class " + className);
				field.annotate(Size.class).param("min", minLength);
			} else if (maxLength != null) {
				log("@Size(null, " + maxLength + "): " + propertyName + " added to class " + className);
				field.annotate(Size.class).param("max", maxLength);
			} else if (length != null) {
				log("@Size(" + length + "," + length + "): " + propertyName + " added to class "
						+ className);
				field.annotate(Size.class).param("min", length).param("max", length);
			}
		}
		if (jpaAnnotations && isSizeAnnotationApplicable(field)) {
			Integer maxLength = simpleType.getFacet("maxLength") == null ? null : Utils.parseInt(simpleType.getFacet(
					"maxLength").getValue().value);
			if (maxLength != null) {
				log("@Column(null, " + maxLength + "): " + propertyName + " added to class " + className);
				field.annotate(Column.class).param("length", maxLength);
			}
		}
		XSFacet maxInclusive = simpleType.getFacet("maxInclusive");
		if (maxInclusive != null && Utils.isNumber(field) && isValidValue(maxInclusive)
				&& !hasAnnotation(field, DecimalMax.class)) {
			log("@DecimalMax(" + maxInclusive.getValue().value + "): " + propertyName
					+ " added to class " + className);
			field.annotate(DecimalMax.class).param("value", maxInclusive.getValue().value);
		}
		XSFacet minInclusive = simpleType.getFacet("minInclusive");
		if (minInclusive != null && Utils.isNumber(field) && isValidValue(minInclusive)
				&& !hasAnnotation(field, DecimalMin.class)) {
			log("@DecimalMin(" + minInclusive.getValue().value + "): " + propertyName
					+ " added to class " + className);
			field.annotate(DecimalMin.class).param("value", minInclusive.getValue().value);
		}

		XSFacet maxExclusive = simpleType.getFacet("maxExclusive");
		if (maxExclusive != null && Utils.isNumber(field) && isValidValue(maxExclusive)
				&& !hasAnnotation(field, DecimalMax.class)) {
			JAnnotationUse annotate = field.annotate(DecimalMax.class);
			if (jsr349) {
				log("@DecimalMax(value = " + maxExclusive.getValue().value + ", inclusive = false): " + propertyName
						+ " added to class " + className);
				annotate.param("value", maxExclusive.getValue().value);
				annotate.param("inclusive", false);
			} else {
				final BigInteger value = new BigInteger(maxExclusive.getValue().value).subtract(BigInteger.ONE);
				log("@DecimalMax(" + value.toString() + "): " + propertyName + " added to class " + className);
				annotate.param("value", value.toString());
			}
		}
		XSFacet minExclusive = simpleType.getFacet("minExclusive");
		if (minExclusive != null && Utils.isNumber(field) && isValidValue(minExclusive)
				&& !hasAnnotation(field, DecimalMin.class)) {
			JAnnotationUse annotate = field.annotate(DecimalMin.class);
			if (jsr349) {
				log("@DecimalMin(value = " + minExclusive.getValue().value + ", inclusive = false): " + propertyName
						+ " added to class " + className);
				annotate.param("value", minExclusive.getValue().value);
				annotate.param("inclusive", false);
			} else {
				final BigInteger value = new BigInteger(minExclusive.getValue().value).add(BigInteger.ONE);
				log("@DecimalMax(" + value.toString() + "): " + propertyName + " added to class " + className);
				annotate.param("value", value.toString());
			}
		}

		if (simpleType.getFacet("totalDigits") != null && Utils.isNumber(field)) {
			Integer totalDigits = simpleType.getFacet("totalDigits") == null ? null
					: Utils.parseInt(simpleType.getFacet("totalDigits").getValue().value);
			int fractionDigits = simpleType.getFacet("fractionDigits") == null ? 0
					: Utils.parseInt(simpleType.getFacet("fractionDigits").getValue().value);
			if (!hasAnnotation(field, Digits.class)) {
				log("@Digits(" + totalDigits + "," + fractionDigits + "): " + propertyName
						+ " added to class " + className);
				JAnnotationUse annox = field.annotate(Digits.class).param("integer", (totalDigits - fractionDigits));
				annox.param("fraction", fractionDigits);
			}
			if (jpaAnnotations) {
				field.annotate(Column.class).param("precision", totalDigits).param("scale", fractionDigits);
			}
		}
		/**
		 * 
		 */
		List patternList = simpleType.getFacets("pattern");
		if (patternList.size() > 1) { // More than one pattern
			log("@Pattern.List: " + propertyName + " added to class " + className);
			JAnnotationUse patternListAnnotation = field.annotate(Pattern.List.class);
			JAnnotationArrayMember listValue = patternListAnnotation.paramArray("value");

			if ("String".equals(field.type().name())) {
				for (XSFacet xsFacet : patternList) {
					final String value = xsFacet.getValue().value;
					// cxf-codegen fix
					if (!"\\c+".equals(value)) {
						listValue.annotate(Pattern.class).param("regexp", replaceXmlProprietals(value));
					}
				}
			}
		} else if (simpleType.getFacet("pattern") != null) {
			String pattern = simpleType.getFacet("pattern").getValue().value;
			if ("String".equals(field.type().name())) {
				// cxf-codegen fix
				if (!"\\c+".equals(pattern)) {
					log("@Pattern(" + pattern + "): " + propertyName + " added to class " + className);
					if (!hasAnnotation(field, Pattern.class)) {
						field.annotate(Pattern.class).param("regexp", replaceXmlProprietals(pattern));
					}
				}
			}
		} else if ("String".equals(field.type().name())) {
			final List enumerationList = simpleType.getFacets("enumeration");
			if (enumerationList.size() > 1) { // More than one pattern
				log("@Pattern.List: " + propertyName + " added to class " + className);
				final JAnnotationUse patternListAnnotation = field.annotate(Pattern.List.class);
				final JAnnotationArrayMember listValue = patternListAnnotation.paramArray("value");
				for (XSFacet xsFacet : enumerationList) {
					final String value = xsFacet.getValue().value;
					// cxf-codegen fix
					if (!"\\c+".equals(value)) {
						listValue.annotate(Pattern.class).param("regexp", replaceXmlProprietals(value));
					}
				}
			} else if (simpleType.getFacet("enumeration") != null) {
				final String pattern = simpleType.getFacet("enumeration").getValue().value;
				// cxf-codegen fix
				if (!"\\c+".equals(pattern)) {
					log("@Pattern(" + pattern + "): " + propertyName + " added to class " + className);
					field.annotate(Pattern.class).param("regexp", replaceXmlProprietals(pattern));
				}
			}
		}
	}

	private String replaceXmlProprietals(String pattern) {
		return pattern.replace("\\i", "[_:A-Za-z]").replace("\\c", "[-._:A-Za-z0-9]");
	}

	private boolean isSizeAnnotationApplicable(JFieldVar field) {
		return field.type().name().equals("String")|| field.type().isArray() ;
	}

	/*attribute from parent declaration*/
	private void processAttribute(CValuePropertyInfo property, ClassOutline clase, Outline model) {
		FieldOutline field = model.getField(property);
		String propertyName = property.getName(false);
		String className = clase.implClass.name();

		log("Attribute " + propertyName + " added to class " + className);
		XSComponent definition = property.getSchemaComponent();
		RestrictionSimpleTypeImpl particle = (RestrictionSimpleTypeImpl) definition;
		XSSimpleType type = particle.asSimpleType();
		JFieldVar var = clase.implClass.fields().get(propertyName);


//		if (particle.isRequired()) {
//			if (!hasAnnotation(var, NotNull.class)) {
//				if (notNullAnnotations) {
//					System.out.println("@NotNull: " + propertyName + " added to class " + className);
//					var.annotate(NotNull.class);
//				}
//			}
//		}

		validAnnotation(type, var, propertyName, className);
		processType(type, var, propertyName, className);
	}

	/**
	 * XS:Attribute
	 */
	public void processAttribute(CAttributePropertyInfo property, ClassOutline clase, Outline model) {
		FieldOutline field = model.getField(property);
		String propertyName = property.getName(false);
		String className = clase.implClass.name();

		log("Attribute " + propertyName + " added to class " + className);
		XSComponent definition = property.getSchemaComponent();
		AttributeUseImpl particle = (AttributeUseImpl) definition;
		XSSimpleType type = particle.getDecl().getType();

		JFieldVar var = clase.implClass.fields().get(propertyName);
		if (particle.isRequired()) {
			if (!hasAnnotation(var, NotNull.class)) {
				processNotNull(clase, var);
			}
		}

		validAnnotation(type, var, propertyName, className);
		processType(type, var, propertyName, className);
	}

	protected boolean isValidValue(XSFacet facet) {
		String value = facet.getValue().value;
		// cxf-codegen puts max and min as value when there is not anything defined in wsdl.
		return value != null && !Utils.isMax(value) && !Utils.isMin(value);
	}

	@SuppressWarnings({"unchecked", "rawtypes"})
	public boolean hasAnnotation(JFieldVar var, Class annotationClass) {
		List list = (List) Utils.getField("annotations", var);
		if (list != null) {
			for (JAnnotationUse annotationUse : list) {
				if (((Class) Utils.getField("clazz._class", annotationUse)).getCanonicalName().equals(
						annotationClass.getCanonicalName())) {
					return true;
				}
			}
		}
		return false;
	}

	private String propertyName(CElementPropertyInfo property) {
		return property.getName(false);
	}


	private void log(Exception e) {
		e.printStackTrace();
	}

	private void log(String log) {
		if (verbose) {
			System.out.println(log);
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy