package com.sun.tools.xjc.addon.krasa; 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.*; 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; import org.xml.sax.ErrorHandler; import javax.persistence.Column; import javax.validation.Valid; import javax.validation.constraints.*; import java.io.IOException; import java.math.BigInteger; import java.util.Collections; import java.util.List; import static com.sun.tools.xjc.addon.krasa.Utils.toInt; /** * 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"; public static final String GENERATE_SERVICE_VALIDATION_ANNOTATIONS = PLUGIN_OPTION_NAME + ":generateServiceValidationAnnotations"; 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 serviceValidationAnnotations = null; 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.length() != 0 && !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++; } int index_serviceValidationAnnotation = arg1.indexOf(GENERATE_SERVICE_VALIDATION_ANNOTATIONS); if (index_serviceValidationAnnotation > 0) { serviceValidationAnnotations = arg1.substring(index_serviceValidationAnnotation + GENERATE_SERVICE_VALIDATION_ANNOTATIONS.length() + "=".length()).trim(); consumed++; } return consumed; } public List<String> 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<CPropertyInfo> 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)); boolean nillable = toBoolean(Utils.getField("nillable",particle.getTerm())); JFieldVar field = classOutline.implClass.fields().get(propertyName(property)); // workaround for choices boolean required = property.isRequired(); if (minOccurs < 0 || minOccurs >= 1 && required && !nillable) { 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 boolean toBoolean(Object field) { if(field != null){ return Boolean.parseBoolean(field.toString()); } return false; } 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() || Utils.isCustomType(var))) { 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); annox.param("fraction", fractionDigits); } if (jpaAnnotations) { field.annotate(Column.class).param("precision", totalDigits).param("scale", fractionDigits); } } /** * <annox:annotate annox:class="javax.validation.constraints.Pattern" * message="Name can only contain capital letters, numbers and the symbols '-', '_', '/', ' '" * regexp="^[A-Z0-9_\s//-]*" /> */ List<XSFacet> patternList = simpleType.getFacets("pattern"); if (patternList.size() > 1) { // More than one pattern if ("String".equals(field.type().name())) { if (simpleType.getBaseType() instanceof XSSimpleType && ((XSSimpleType) simpleType.getBaseType()) .getFacet("pattern") != null) { log("@Pattern.List: " + propertyName + " added to class " + className); JAnnotationUse patternListAnnotation = field.annotate(Pattern.List.class); JAnnotationArrayMember listValue = patternListAnnotation.paramArray("value"); String basePattern = ((XSSimpleType) simpleType.getBaseType()).getFacet("pattern").getValue().value; listValue.annotate(Pattern.class).param("regexp", replaceXmlProprietals(basePattern)); log("@Pattern: " + propertyName + " added to class " + className); final JAnnotationUse patternAnnotation = listValue.annotate(Pattern.class); annotateMultiplePattern(patternList, patternAnnotation); } else { log("@Pattern: " + propertyName + " added to class " + className); final JAnnotationUse patternAnnotation = field.annotate(Pattern.class); annotateMultiplePattern(patternList, patternAnnotation); } } } else if (simpleType.getFacet("pattern") != null) { String pattern = simpleType.getFacet("pattern").getValue().value; if ("String".equals(field.type().name())) { if (simpleType.getBaseType() instanceof XSSimpleType && ((XSSimpleType) simpleType.getBaseType()) .getFacet("pattern") != null) { log("@Pattern.List: " + propertyName + " added to class " + className); JAnnotationUse patternListAnnotation = field.annotate(Pattern.List.class); JAnnotationArrayMember listValue = patternListAnnotation.paramArray("value"); String basePattern = ((XSSimpleType) simpleType.getBaseType()).getFacet("pattern").getValue().value; listValue.annotate(Pattern.class).param("regexp", replaceXmlProprietals(basePattern)); // cxf-codegen fix if (!"\\c+".equals(pattern)) { log("@Pattern(" + pattern + "): " + propertyName + " added to class " + className); if (!hasAnnotation(field, Pattern.class)) { listValue.annotate(Pattern.class).param("regexp", replaceXmlProprietals(pattern)); } } } else { // 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<XSFacet> enumerationList = simpleType.getFacets("enumeration"); if (enumerationList.size() > 1) { // More than one pattern log("@Pattern: " + propertyName + " added to class " + className); final JAnnotationUse patternListAnnotation = field.annotate(Pattern.class); annotateMultiplePattern(enumerationList, patternListAnnotation); } 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 void annotateMultiplePattern(final List<XSFacet> patternList, final JAnnotationUse patternAnnotation) { StringBuilder sb = new StringBuilder(); for (XSFacet xsFacet : patternList) { final String value = xsFacet.getValue().value; // cxf-codegen fix if (!"\\c+".equals(value)) { sb.append("(").append(replaceXmlProprietals(value)).append(")|"); } } patternAnnotation.param("regexp", sb.substring(0, sb.length() - 1)); } 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<JAnnotationUse> list = (List<JAnnotationUse>) 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); } } }