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    }