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 }