View Javadoc
1   /*
2    * Copyright 2010 The Kuali Foundation.
3    * 
4    * Licensed under the Educational Community License, Version 1.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl1.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.sys.context;
17  
18  import java.util.ArrayList;
19  import java.util.Collection;
20  import java.util.Map;
21  
22  import org.apache.commons.lang.StringUtils;
23  import org.kuali.rice.kns.service.DataDictionaryService;
24  
25  /**
26   * Builder for XML schema types based on a data dictionary attribute. Data dictionary properties such as required, maxLength, and
27   * validation pattern are retrieved and then the equivalent schema restriction is rendered for the type
28   */
29  public class AttributeSchemaValidationBuilder {
30      protected static final String DD_MAP_MAX_LENGTH_KEY = "maxLength";
31      protected static final String DD_MAP_EXACT_LENGTH_KEY = "validationPattern.exactLength";
32      protected static final String DD_MAP_REQUIRED_KEY = "required";
33      protected static final String DD_MAP_EXCLUSIVE_MIN_KEY = "exclusiveMin";
34      protected static final String DD_MAP_EXCLUSIVE_MAX_KEY = "exclusiveMax";
35      protected static final String DD_MAP_VALIDATION_KEY = "validationPattern";
36      protected static final String DD_MAP_VALIDATION_TYPE_KEY = "type";
37      protected static final String DD_ALLOW_WHITESPACE_KEY = "validationPattern.allowWhitespace";
38  
39      public static class DD_VALIDATION_TYPES {
40          public static final String DATE = "date";
41          public static final String EMAIL = "emailAddress";
42          public static final String FIXED_POINT = "fixedPoint";
43          public static final String FLOATING_POINT = "floatingPoint";
44          public static final String MONTH = "month";
45          public static final String PHONE_NUMBER = "phoneNumber";
46          public static final String TIMESTAMP = "timestamp";
47          public static final String YEAR = "year";
48          public static final String ZIP_CODE = "zipcode";
49          public static final String ALPHA_NUMBER = "alphaNumeric";
50          public static final String ALPHA = "alpha";
51          public static final String ANY_CHARACTER = "anyCharacter";
52          public static final String CHARSET = "charset";
53          public static final String NUMBERIC = "numeric";
54          public static final String REGEX = "regex";
55      }
56  
57      public static final String XSD_SCHEMA_PREFIX = "xsd:";
58  
59      public static class SCHEMA_BASE_TYPES {
60          public static final String DATE = "date";
61          public static final String DATE_TIME = "dateTime";
62          public static final String STRING = "normalizedString";
63          public static final String INTEGER = "integer";
64          public static final String DECIMAL = "decimal";
65      }
66  
67      protected String attributeKey;
68      protected Map attributeMap;
69  
70      /**
71       * Constructs a AttributeSchemaValidationBuilder.java.
72       */
73      public AttributeSchemaValidationBuilder() {
74  
75      }
76  
77      /**
78       * Constructs a AttributeSchemaValidationBuilder.java.
79       * 
80       * @param attributeKey name of data dictionary entry to build type for
81       */
82      public AttributeSchemaValidationBuilder(String attributeKey) {
83          this.attributeKey = attributeKey;
84  
85          Map dataDictionaryMap = SpringContext.getBean(DataDictionaryService.class).getDataDictionaryMap();
86          String boClassName = StringUtils.substringBefore(attributeKey, ".");
87          String attributeName = StringUtils.substringAfter(attributeKey, ".");
88  
89          Map boMap = (Map) dataDictionaryMap.get(boClassName);
90          if (boMap == null) {
91              throw new RuntimeException("Unable to find bo map for class: " + boClassName);
92          }
93          
94          Map attributesMap = (Map) boMap.get("attributes");
95          this.attributeMap = (Map) attributesMap.get(attributeName);
96          if (this.attributeMap == null) {
97              throw new RuntimeException("Unable to find export map for attribute: " + attributeKey);
98          }
99      }
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 }