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}