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