001 /* 002 * Copyright 2011 The Kuali Foundation. 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.kfs.sys.context; 017 018 import java.util.ArrayList; 019 import java.util.Collection; 020 import java.util.Map; 021 022 import org.apache.commons.lang.StringUtils; 023 import org.kuali.kfs.sys.KFSConstants; 024 import org.kuali.rice.kns.service.DataDictionaryService; 025 026 /** 027 * Builder for XML schema types based on a data dictionary attribute. Data dictionary properties such as required, maxLength, and 028 * validation pattern are retrieved and then the equivalent schema restriction is rendered for the type 029 */ 030 public class AttributeSchemaValidationBuilder { 031 protected static final String DD_MAP_MAX_LENGTH_KEY = "maxLength"; 032 protected static final String DD_MAP_EXACT_LENGTH_KEY = "validationPattern.exactLength"; 033 protected static final String DD_MAP_REQUIRED_KEY = "required"; 034 protected static final String DD_MAP_EXCLUSIVE_MIN_KEY = "exclusiveMin"; 035 protected static final String DD_MAP_EXCLUSIVE_MAX_KEY = "exclusiveMax"; 036 protected static final String DD_MAP_VALIDATION_KEY = "validationPattern"; 037 protected static final String DD_MAP_VALIDATION_TYPE_KEY = "type"; 038 protected static final String DD_ALLOW_WHITESPACE_KEY = "validationPattern.allowWhitespace"; 039 040 public static class DD_VALIDATION_TYPES { 041 public static final String DATE = "date"; 042 public static final String EMAIL = "emailAddress"; 043 public static final String FIXED_POINT = "fixedPoint"; 044 public static final String FLOATING_POINT = "floatingPoint"; 045 public static final String MONTH = "month"; 046 public static final String PHONE_NUMBER = "phoneNumber"; 047 public static final String TIMESTAMP = "timestamp"; 048 public static final String YEAR = "year"; 049 public static final String ZIP_CODE = "zipcode"; 050 public static final String ALPHA_NUMBER = "alphaNumeric"; 051 public static final String ALPHA = "alpha"; 052 public static final String ANY_CHARACTER = "anyCharacter"; 053 public static final String CHARSET = "charset"; 054 public static final String NUMBERIC = "numeric"; 055 public static final String REGEX = "regex"; 056 } 057 058 public static final String XSD_SCHEMA_PREFIX = "xsd:"; 059 060 public static class SCHEMA_BASE_TYPES { 061 public static final String DATE = "date"; 062 public static final String DATE_TIME = "dateTime"; 063 public static final String STRING = "normalizedString"; 064 public static final String INTEGER = "integer"; 065 public static final String DECIMAL = "decimal"; 066 } 067 068 protected String attributeKey; 069 protected Map attributeMap; 070 071 /** 072 * Constructs a AttributeSchemaValidationBuilder.java. 073 */ 074 public AttributeSchemaValidationBuilder() { 075 076 } 077 078 /** 079 * Constructs a AttributeSchemaValidationBuilder.java. 080 * 081 * @param attributeKey name of data dictionary entry to build type for 082 */ 083 public AttributeSchemaValidationBuilder(String attributeKey) { 084 this.attributeKey = attributeKey; 085 086 Map dataDictionaryMap = SpringContext.getBean(DataDictionaryService.class).getDataDictionaryMap(); 087 String boClassName = StringUtils.substringBefore(attributeKey, "."); 088 String attributeName = StringUtils.substringAfter(attributeKey, "."); 089 090 Map boMap = (Map) dataDictionaryMap.get(boClassName); 091 if (boMap == null) { 092 throw new RuntimeException("Unable to find bo map for class: " + boClassName); 093 } 094 095 Map attributesMap = (Map) boMap.get("attributes"); 096 this.attributeMap = (Map) attributesMap.get(attributeName); 097 if (this.attributeMap == null) { 098 throw new RuntimeException("Unable to find export map for attribute: " + attributeKey); 099 } 100 } 101 102 /** 103 * Based on data dictionary configuration for attribute builds a complete schema simple type with the appropriate base and 104 * restrictions 105 * 106 * @return collection of XML lines for the type 107 */ 108 public Collection toSchemaType() { 109 Collection schemaType = new ArrayList(); 110 111 schemaType.add(getTypeTagOpener()); 112 schemaType.add(getRestrictionTagOpener()); 113 schemaType.addAll(getFurtherRestrictionTags()); 114 schemaType.add(getRestrictionTagCloser()); 115 schemaType.add(getTypeTagCloser()); 116 117 return schemaType; 118 } 119 120 /** 121 * Builds simple type opening tag . 122 * 123 * @return XML Line 124 */ 125 public String getTypeTagOpener() { 126 return String.format(" <%ssimpleType name=\"%s\">", XSD_SCHEMA_PREFIX, attributeKey); 127 } 128 129 /** 130 * Builds simple type closing tag . 131 * 132 * @return XML Line 133 */ 134 public String getTypeTagCloser() { 135 return String.format(" </%ssimpleType>", XSD_SCHEMA_PREFIX); 136 } 137 138 /** 139 * Builds restriction opening tag. Data dictionary validation type is used to determine base schema type . 140 * 141 * @return XML Line 142 */ 143 public String getRestrictionTagOpener() { 144 String xsdBase = ""; 145 if (isDateType()) { 146 xsdBase = XSD_SCHEMA_PREFIX + SCHEMA_BASE_TYPES.DATE; 147 } 148 else if (isTimestampType()) { 149 xsdBase = XSD_SCHEMA_PREFIX + SCHEMA_BASE_TYPES.DATE_TIME; 150 } 151 else if (isDecimalType() || isFloatingType()) { 152 xsdBase = XSD_SCHEMA_PREFIX + SCHEMA_BASE_TYPES.DECIMAL; 153 } 154 else if (isNumericType()) { 155 xsdBase = XSD_SCHEMA_PREFIX + SCHEMA_BASE_TYPES.INTEGER; 156 } 157 else { 158 xsdBase = XSD_SCHEMA_PREFIX + SCHEMA_BASE_TYPES.STRING; 159 } 160 161 return String.format(" <%srestriction base=\"%s\">", XSD_SCHEMA_PREFIX, xsdBase); 162 } 163 164 /** 165 * Builds restriction closing tag 166 * 167 * @return XML Line 168 */ 169 public String getRestrictionTagCloser() { 170 return String.format(" </%srestriction>", XSD_SCHEMA_PREFIX); 171 } 172 173 /** 174 * Based on attribute definition adds any further restrictions to the type 175 * 176 * @return collection of XML lines 177 */ 178 public Collection getFurtherRestrictionTags() { 179 Collection restrictions = new ArrayList(); 180 181 // required can be applied to all types 182 boolean required = getRequiredFromMap(); 183 if (required) { 184 if (isStringType()) { 185 restrictions.add(String.format(" <%sminLength value=\"1\"/>", XSD_SCHEMA_PREFIX)); 186 } 187 else { 188 restrictions.add(String.format(" <%spattern value=\"[^\\s]+\"/>", XSD_SCHEMA_PREFIX)); 189 } 190 } 191 192 if (isDateType() || isTimestampType() || isFloatingType()) { 193 return restrictions; 194 } 195 196 if (isDecimalType()) { 197 restrictions.add(String.format(" <%sfractionDigits value=\"2\"/>", XSD_SCHEMA_PREFIX)); 198 return restrictions; 199 } 200 201 if (isNumericType()) { 202 String exclusiveMin = (String) attributeMap.get(DD_MAP_EXCLUSIVE_MIN_KEY); 203 String exclusiveMax = (String) attributeMap.get(DD_MAP_EXCLUSIVE_MAX_KEY); 204 if (StringUtils.isNotBlank(exclusiveMin)) { 205 restrictions.add(String.format(" <%sminExclusive value=\"%s\"/>", XSD_SCHEMA_PREFIX, exclusiveMin)); 206 } 207 if (StringUtils.isNotBlank(exclusiveMax)) { 208 restrictions.add(String.format(" <%smaxExclusive value=\"%s\"/>", XSD_SCHEMA_PREFIX, exclusiveMax)); 209 } 210 211 int exactLength = getExactLengthFromMap(); 212 if (exactLength > 0) { 213 restrictions.add(String.format(" <%stotalDigits value=\"%s\"/>", XSD_SCHEMA_PREFIX, exactLength)); 214 } 215 216 return restrictions; 217 } 218 219 // here we are dealing with string types 220 int maxLength = getMaxLengthFromMap(); 221 if (maxLength > 0) { 222 restrictions.add(String.format(" <%smaxLength value=\"%s\"/>", XSD_SCHEMA_PREFIX, maxLength)); 223 } 224 225 int exactLength = getExactLengthFromMap(); 226 if (exactLength > 0) { 227 restrictions.add(String.format(" <%slength value=\"%s\"/>", XSD_SCHEMA_PREFIX, exactLength)); 228 } 229 230 boolean collapseWhitespace = !getAllowWhitespaceFromMap(); 231 if (collapseWhitespace) { 232 restrictions.add(String.format(" <%swhiteSpace value=\"replace\"/>", XSD_SCHEMA_PREFIX)); 233 } 234 235 return restrictions; 236 } 237 238 /** 239 * Helper method to get the max length from the dd map 240 * 241 * @return max length, or -1 if not found 242 */ 243 protected int getMaxLengthFromMap() { 244 String maxLengthStr = (String) attributeMap.get(DD_MAP_MAX_LENGTH_KEY); 245 if (StringUtils.isNotBlank(maxLengthStr)) { 246 int maxLength = Integer.parseInt(maxLengthStr); 247 248 return maxLength; 249 } 250 251 return -1; 252 } 253 254 /** 255 * Helper method to get the exact length from the dd Map 256 * 257 * @return exact length or -1 if not found 258 */ 259 protected int getExactLengthFromMap() { 260 String exactLengthStr = (String) attributeMap.get(DD_MAP_EXACT_LENGTH_KEY); 261 if (StringUtils.isNotBlank(exactLengthStr)) { 262 int exactLength = Integer.parseInt(exactLengthStr); 263 264 return exactLength; 265 } 266 267 return -1; 268 } 269 270 /** 271 * Helper method to get the required setting from dd Map 272 * 273 * @return true if required setting is set to true in dd, false if setting is false or was not found 274 */ 275 protected boolean getRequiredFromMap() { 276 String requiredStr = (String) attributeMap.get(DD_MAP_REQUIRED_KEY); 277 if (StringUtils.isNotBlank(requiredStr)) { 278 boolean required = Boolean.parseBoolean(requiredStr); 279 280 return required; 281 } 282 283 return false; 284 } 285 286 /** 287 * Helper method to get the allow whitespace setting from dd Map 288 * 289 * @return true if allow whitespace setting is set to true in dd, false if setting is false or was not found 290 */ 291 protected boolean getAllowWhitespaceFromMap() { 292 String whitespaceStr = (String) attributeMap.get(DD_ALLOW_WHITESPACE_KEY); 293 if (StringUtils.isNotBlank(whitespaceStr)) { 294 boolean allowWhitespace = Boolean.parseBoolean(whitespaceStr); 295 296 return allowWhitespace; 297 } 298 299 return false; 300 } 301 302 /** 303 * Helper method to get validation type from dd Map 304 * 305 * @return dd validation type 306 */ 307 protected String getValidationType() { 308 String validationType = ""; 309 Map validationMap = (Map) attributeMap.get(DD_MAP_VALIDATION_KEY); 310 if (validationMap != null) { 311 validationType = (String) validationMap.get(DD_MAP_VALIDATION_TYPE_KEY); 312 } 313 314 return validationType; 315 } 316 317 /** 318 * Determines if the attribute's validation type is the Date validation type 319 * 320 * @return boolean true if type is Date, false otherwise 321 */ 322 protected boolean isDateType() { 323 return DD_VALIDATION_TYPES.DATE.equals(getValidationType()); 324 } 325 326 /** 327 * Determines if the attribute's validation type is the Timestamp validation type 328 * 329 * @return boolean true if type is Timestamp, false otherwise 330 */ 331 protected boolean isTimestampType() { 332 return DD_VALIDATION_TYPES.TIMESTAMP.equals(getValidationType()); 333 } 334 335 /** 336 * Determines if the attribute's validation type is the Numeric validation type 337 * 338 * @return boolean true if type is Numeric, false otherwise 339 */ 340 protected boolean isNumericType() { 341 return DD_VALIDATION_TYPES.NUMBERIC.equals(getValidationType()); 342 } 343 344 /** 345 * Determines if the attribute's validation type is the Decimal validation type 346 * 347 * @return boolean true if type is Decimal, false otherwise 348 */ 349 protected boolean isDecimalType() { 350 return DD_VALIDATION_TYPES.FIXED_POINT.equals(getValidationType()); 351 } 352 353 /** 354 * Determines if the attribute's validation type is the Floating validation type 355 * 356 * @return boolean true if type is Floating, false otherwise 357 */ 358 protected boolean isFloatingType() { 359 return DD_VALIDATION_TYPES.FLOATING_POINT.equals(getValidationType()); 360 } 361 362 /** 363 * Determines if the attribute's validation type is a String validation type 364 * 365 * @return boolean true if type is String, false otherwise 366 */ 367 protected boolean isStringType() { 368 return !isDateType() && !isTimestampType() && !isNumericType() && !isDecimalType() && !isFloatingType(); 369 } 370 371 /** 372 * Gets the attributeKey attribute. 373 * 374 * @return Returns the attributeKey. 375 */ 376 public String getAttributeKey() { 377 return attributeKey; 378 } 379 380 /** 381 * Sets the attributeKey attribute value. 382 * 383 * @param attributeKey The attributeKey to set. 384 */ 385 public void setAttributeKey(String attributeKey) { 386 this.attributeKey = attributeKey; 387 } 388 389 390 }