/* * Copyright 2001-2008 Geert Bevin <gbevin[remove] at uwyn dot com> * Licensed under the Apache License, Version 2.0 (the "License") * $Id: AbstractValidationBuilder.java 3918 2008-04-14 17:35:35Z gbevin $ */ package com.uwyn.rife.site; import java.util.*; import java.util.logging.Logger; import com.uwyn.rife.site.exceptions.MissingMarkingBlockException; import com.uwyn.rife.site.exceptions.ValidationBuilderException; import com.uwyn.rife.template.InternalValue; import com.uwyn.rife.template.Template; import com.uwyn.rife.template.exceptions.TemplateException; import com.uwyn.rife.tools.ExceptionUtils; public abstract class AbstractValidationBuilder implements ValidationBuilder { public void setFallbackErrorArea(Template template, String message) { if (null == template) { return; } if (null == message) { message = ""; } if (template.hasValueId(ID_ERRORS) && template.hasBlock(ID_ERRORS_FALLBACK)) { if (template.hasValueId(ID_ERRORMESSAGE) && template.hasBlock(ID_ERRORMESSAGE_WILDCARD)) { template.setValue(ID_ERRORMESSAGE, message); template.setBlock(ID_ERRORS, ID_ERRORMESSAGE_WILDCARD); template.setBlock(ID_ERRORS_WILDCARD, ID_ERRORS_FALLBACK); template.removeValue(ID_ERRORMESSAGE); template.removeValue(ID_ERRORS); } else { template.setValue(ID_ERRORS, message); template.setBlock(ID_ERRORS_WILDCARD, ID_ERRORS_FALLBACK); template.removeValue(ID_ERRORS); } } else if (template.hasValueId(ID_ERRORS) && template.hasBlock(ID_ERRORS_WILDCARD)) { if (template.hasValueId(ID_ERRORMESSAGE) && template.hasBlock(ID_ERRORMESSAGE_WILDCARD)) { template.setValue(ID_ERRORMESSAGE, message); template.setBlock(ID_ERRORS, ID_ERRORMESSAGE_WILDCARD); template.setBlock(ID_ERRORS_WILDCARD, ID_ERRORS_WILDCARD); template.removeValue(ID_ERRORMESSAGE); template.removeValue(ID_ERRORS); } else { template.setValue(ID_ERRORS, message); template.setBlock(ID_ERRORS_WILDCARD, ID_ERRORS_WILDCARD); template.removeValue(ID_ERRORS); } } else { template.setValue(ID_ERRORS_WILDCARD, message); } } public Collection<String> generateValidationErrors(Template template, Collection<ValidationError> errors, Collection<String> onlySubjectsToClear, String prefix) { if (null == template || null == errors || 0 == errors.size()) { return Collections.EMPTY_LIST; } ArrayList<String> set_values = new ArrayList<String>(); // adapt for subject prefixes if (prefix != null && onlySubjectsToClear != null) { ArrayList<String> prefixed_subjects = new ArrayList<String>(); for (String property : onlySubjectsToClear) { prefixed_subjects.add(prefix+property); } onlySubjectsToClear = prefixed_subjects; } // check if validation errors are already present in the bean, // and generate the formatted errors boolean has_wildcard_errors_block = template.hasBlock(ID_ERRORS_WILDCARD); boolean has_fallback_errors_block = template.hasBlock(ID_ERRORS_FALLBACK); boolean has_fallback_errors_value = template.hasValueId(ID_ERRORS_WILDCARD); InternalValue fallback_errors_construction = null; if (has_fallback_errors_value) { template.removeValue(ID_ERRORS_WILDCARD); fallback_errors_construction = template.createInternalValue(); } // Reorder the filtered error values so that they're ordered according // to the number of properties they declare. Those with the same // number of properties will be ordered according to the their order // of declaration. // Also, all the content from the filtered values will be cleared to // ensure that errors from previous submissions will not prevail. List<String[]> filtered_errorvalues = template.getFilteredValues(TAG_ERRORS); ArrayList<List<String[]>> sorted_errorvalues = null; if (filtered_errorvalues.size() > 0) { sorted_errorvalues = new ArrayList<List<String[]>>(); List<String[]> errorvalues; for (String[] filtered_value : filtered_errorvalues) { // only clear the filtered value if one of the subjects is // validated if (null == onlySubjectsToClear) { template.removeValue(filtered_value[0]); } else { for (int i = 1; i < filtered_value.length; i++) { if (onlySubjectsToClear.contains(filtered_value[i])) { template.removeValue(filtered_value[0]); break; } } } if (filtered_value.length-1 < sorted_errorvalues.size()) { errorvalues = sorted_errorvalues.get(filtered_value.length-1); } else { errorvalues = null; while (!(filtered_value.length-1 < sorted_errorvalues.size())) { sorted_errorvalues.add(null); } } if (null == errorvalues) { errorvalues = new ArrayList<String[]>(); sorted_errorvalues.set(filtered_value.length-1, errorvalues); } errorvalues.add(filtered_value); } } // Reorder the filtered error blocks so that they're ordered according // to the number of properties they declare. Those with the same // number of properties will be ordered according to the their order // of declaration. List<String[]> filtered_errorblocks = template.getFilteredBlocks(TAG_ERRORS); ArrayList<List<String[]>> sorted_errorblocks = null; if (filtered_errorblocks.size() > 0) { sorted_errorblocks = new ArrayList<List<String[]>>(); List<String[]> errorblocks; for (String[] filtered_block : filtered_errorblocks) { if (filtered_block.length-1 < sorted_errorblocks.size()) { errorblocks = sorted_errorblocks.get(filtered_block.length-1); } else { errorblocks = null; while (!(filtered_block.length-1 < sorted_errorblocks.size())) { sorted_errorblocks.add(null); } } if (null == errorblocks) { errorblocks = new ArrayList<String[]>(); sorted_errorblocks.set(filtered_block.length-1, errorblocks); } errorblocks.add(filtered_block); } } // Re-arrange the filtered blocks to be able to easily select the // block id that corresponds to an error value. // The block have been sorted so that the most specific one // (less properties) is considered before a more general one // (more properties). When they have the same specificity they // are processed according to their order of declaration. // A block id with 'property1' will thus gain precedence // over a block id with 'property1,property2', but when only the // latter is defined it will be used even if either property // has an error individually. LinkedHashMap<String, ArrayList<String>> block_properties_mapping = null; if (sorted_errorblocks != null) { block_properties_mapping = new LinkedHashMap<String, ArrayList<String>>(); ArrayList<String> block_properties; for (List<String[]> categorized_errorblocks : sorted_errorblocks) { if (null == categorized_errorblocks) { continue; } for (String[] filtered_block : categorized_errorblocks) { block_properties = new ArrayList<String>(); block_properties_mapping.put(filtered_block[0], block_properties); for (int i = 1; i < filtered_block.length; i++) { block_properties.add(filtered_block[i]); } } } } Collection<String> invalid_subjects = collectSubjects(errors, prefix); // Go over the error values according to their order of importance. // Values with a broader scope (more properties) get precedence of // those with a narrower scope (less properties) and inside the // same scope level they are handled according to their order // of declaration. // For each value that is used to report errors in, an internal // construction variable is created. It is there that the error // messages will be appended. // The formatting block that corresponds best to the used value // is also determined and stored for later retrieval during // the template construction. HashMap<String, InternalValue> values_construction = null; HashMap<String, String> property_value_mapping = null; HashMap<String, String> values_block_mapping = null; if (filtered_errorvalues.size() > 0) { values_construction = new HashMap<String, InternalValue>(); property_value_mapping = new HashMap<String, String>(); values_block_mapping = new HashMap<String, String>(); List<String[]> errorvalues; for (int i = sorted_errorvalues.size()-1; i >= 0; i--) { errorvalues = sorted_errorvalues.get(i); if (null == errorvalues) { continue; } for (String[] filtered_value : errorvalues) { // check if the filtered value contains only subjects // that are invalid boolean use_value = true; for (int j = 1; j < filtered_value.length; j++) { if (!invalid_subjects.contains(filtered_value[j])) { use_value = false; break; } } // If the value can be used, register its id for // all the declared subjects. If the subject is already // bound to another value, it is simply skipped. if (use_value) { boolean tied_to_properties = false; for (int j = 1; j < filtered_value.length; j++) { if (!property_value_mapping.containsKey(filtered_value[j])) { property_value_mapping.put(filtered_value[j], filtered_value[0]); tied_to_properties = true; } } if (tied_to_properties) { // prepare an internal value to construct the value values_construction.put(filtered_value[0], template.createInternalValue()); // go over all the error blocks until one is found that supports the // same properties as the value ArrayList<String> error_block_properties; if (block_properties_mapping != null) { for (Map.Entry<String, ArrayList<String>> error_block : block_properties_mapping.entrySet()) { boolean matching_block = true; error_block_properties = error_block.getValue(); for (int j = 1; j < filtered_value.length; j++) { if (!error_block_properties.contains(filtered_value[j])) { matching_block = false; break; } } if (matching_block) { values_block_mapping.put(filtered_value[0], error_block.getKey()); break; } } } } } } } } // Process the sorted error message blocks so that for each // property the first occuring block will be used. List<String[]> filtered_errormessages = template.getFilteredBlocks(TAG_ERRORMESSAGE); HashMap<String, String> property_errormessages_mapping = null; if (filtered_errormessages.size() > 0) { property_errormessages_mapping = new HashMap<String, String>(); for (String[] filtered_errormessage : filtered_errormessages) { for (int i = 1; i < filtered_errormessage.length; i++) { if (property_errormessages_mapping.containsKey(filtered_errormessage[i])) { continue; } property_errormessages_mapping.put(filtered_errormessage[i], filtered_errormessage[0]); } } } // Go over all the validation errors and format them according to // their subject. String property_value; InternalValue value_construction; String subject; for (ValidationError error : errors) { subject = error.getSubject(); if (prefix != null) { subject = prefix+subject; } // get the value where the error has to be displayed if (property_value_mapping != null) { property_value = property_value_mapping.get(subject); } else { property_value = null; } if (property_value != null) { value_construction = values_construction.get(property_value); // generate the error message generateErrorMessage(template, error, prefix, value_construction, property_errormessages_mapping); } else if (has_fallback_errors_value) { // generate the error message generateErrorMessage(template, error, prefix, fallback_errors_construction, property_errormessages_mapping); } } // Now that all the values have been constructed, go over // the corresponding blocks to format the content. if (values_construction != null) { for (Map.Entry<String, InternalValue> property_value_id : values_construction.entrySet()) { if (values_block_mapping != null && values_block_mapping.containsKey(property_value_id.getKey())) { template.setValue(ID_ERRORS, property_value_id.getValue()); template.setBlock(property_value_id.getKey(), values_block_mapping.get(property_value_id.getKey())); template.removeValue(ID_ERRORS); } else if (has_wildcard_errors_block) { template.setValue(ID_ERRORS, property_value_id.getValue()); template.setBlock(property_value_id.getKey(), ID_ERRORS_WILDCARD); template.removeValue(ID_ERRORS); } else { template.setValue(property_value_id.getKey(), property_value_id.getValue()); } set_values.add(property_value_id.getKey()); } } // Set the global errors value content. if (fallback_errors_construction != null && !fallback_errors_construction.isEmpty()) { if (has_fallback_errors_block) { template.setValue(ID_ERRORS, fallback_errors_construction); template.setBlock(ID_ERRORS_WILDCARD, ID_ERRORS_FALLBACK); template.removeValue(ID_ERRORS); } else if (has_wildcard_errors_block) { template.setValue(ID_ERRORS, fallback_errors_construction); template.setBlock(ID_ERRORS_WILDCARD, ID_ERRORS_WILDCARD); template.removeValue(ID_ERRORS); } else { template.setValue(ID_ERRORS_WILDCARD, fallback_errors_construction); } set_values.add(ID_ERRORS_WILDCARD); } return set_values; } private Collection<String> collectSubjects(Collection<ValidationError> errors, String prefix) { // Collect the invalid subjects in a seperate collection which // only contains their name. ArrayList<String> invalid_subjects = new ArrayList<String>(); String subject; for (ValidationError error : errors) { subject = error.getSubject(); if (prefix != null) { subject = prefix+subject; } if (!invalid_subjects.contains(error.getSubject())) { invalid_subjects.add(subject); } } return invalid_subjects; } private String generateErrorBlockId(ValidationError error, String prefix) { StringBuilder result = new StringBuilder(error.getIdentifier()); result.append(":"); if (prefix != null) { result.append(prefix); } result.append(error.getSubject()); return result.toString(); } private String generateFallbackSubjectBlockId(ValidationError error, String prefix) { StringBuilder result = new StringBuilder(PREFIX_ERROR); if (prefix != null) { result.append(prefix); } result.append(error.getSubject()); return result.toString(); } private String generateFallbackIdentifierBlockId(ValidationError error) { StringBuilder result = new StringBuilder(error.getIdentifier()); result.append(":*"); return result.toString(); } private void generateErrorMessage(Template template, ValidationError error, String prefix, InternalValue valueConstruction, HashMap<String, String> errormessagesMapping) throws TemplateException { // try to obtain the id of a block that formats each error message // for the provided error String errormessage_block_id = null; String subject = error.getSubject(); if (prefix != null) { subject = prefix+subject; } if (errormessagesMapping != null) { errormessage_block_id = errormessagesMapping.get(subject); } if (null == errormessage_block_id && template.hasBlock(ID_ERRORMESSAGE_WILDCARD)) { errormessage_block_id = ID_ERRORMESSAGE_WILDCARD; } // if a custom error message formatting block was found, set the // actual error message and append the formatted result to the // error construction if (errormessage_block_id != null) { if (template.hasValueId(ID_ERRORMESSAGE)) { String errorblock_id = generateErrorBlockId(error, prefix); // support the IDENTIFIER:subject block id if (template.hasBlock(errorblock_id)) { template.setBlock(ID_ERRORMESSAGE, errorblock_id); } else { String fallback_subject_errorblock_id = generateFallbackSubjectBlockId(error, prefix); // support the ERROR:subject block id if (template.hasBlock(fallback_subject_errorblock_id)) { template.setBlock(ID_ERRORMESSAGE, fallback_subject_errorblock_id); } else { // support the IDENTIFIER:* block id String fallback_identifier_errorblock_id = generateFallbackIdentifierBlockId(error); if (template.hasBlock(fallback_identifier_errorblock_id)) { template.setBlock(ID_ERRORMESSAGE, fallback_identifier_errorblock_id); } else { // support the ERROR:* block id if (template.hasBlock(ID_ERROR_WILDCARD)) { template.setBlock(ID_ERRORMESSAGE, ID_ERROR_WILDCARD); } else { // just output an IDENTIFIER:subject string template.setValue(ID_ERRORMESSAGE, errorblock_id); } } } } valueConstruction.appendBlock(errormessage_block_id); template.removeValue(ID_ERRORMESSAGE); } else { valueConstruction.appendBlock(errormessage_block_id); } } // append a generic error message to the error construction else { String error_value_id = generateErrorBlockId(error, prefix); if (template.hasBlock(error_value_id)) { valueConstruction.appendBlock(error_value_id); } else { String errorblock_id = generateErrorBlockId(error, prefix); // support the IDENTIFIER:subject block id if (template.hasBlock(errorblock_id)) { valueConstruction.appendBlock(errorblock_id); } else { String fallback_subject_errorblock_id = generateFallbackSubjectBlockId(error, prefix); // support the ERROR:subject block id if (template.hasBlock(fallback_subject_errorblock_id)) { valueConstruction.appendBlock(fallback_subject_errorblock_id); } else { // support the IDENTIFIER:* block id String fallback_identifier_errorblock_id = generateFallbackIdentifierBlockId(error); if (template.hasBlock(fallback_identifier_errorblock_id)) { valueConstruction.appendBlock(fallback_identifier_errorblock_id); } else { // support the ERROR:* block id if (template.hasBlock(ID_ERROR_WILDCARD)) { valueConstruction.appendBlock(ID_ERROR_WILDCARD); } else { // just output an IDENTIFIER:subject string valueConstruction.appendValue(formatLine(errorblock_id)); } } } } } } } protected abstract String formatLine(String content); public Collection<String> generateErrorMarkings(Template template, Collection<ValidationError> errors, Collection<String> onlySubjectsToClear, String prefix) throws ValidationBuilderException { if (null == template || null == errors || 0 == errors.size()) { return Collections.EMPTY_LIST; } ArrayList<String> set_values = new ArrayList<String>(); // adapt for subject prefixes if (prefix != null && onlySubjectsToClear != null) { ArrayList<String> prefixed_subjects = new ArrayList<String>(); for (String property : onlySubjectsToClear) { prefixed_subjects.add(prefix+property); } onlySubjectsToClear = prefixed_subjects; } // Reorder the filtered mark so that they're ordered according // to the number of subjects they declare for each mark extension. // Those with the same number of subjects will be ordered according // to the their order of declaration. // Also, all the content from the filtered marks will be cleared to // ensure that errors from previous submissions will not prevail. List<String[]> filtered_marks = template.getFilteredValues(TAG_MARK); HashMap<String, ArrayList<List<String[]>>> sorted_marks_map = null; if (filtered_marks.size() > 0) { sorted_marks_map = new HashMap<String, ArrayList<List<String[]>>>(); ArrayList<List<String[]>> sorted_marks; List<String[]> marks; for (String[] filtered_mark : filtered_marks) { // only clear the filtered mark if one of the subjects is // validated if (null == onlySubjectsToClear) { template.removeValue(filtered_mark[0]); } else { for (int i = 1; i < filtered_mark.length; i++) { if (onlySubjectsToClear.contains(filtered_mark[i])) { template.removeValue(filtered_mark[0]); break; } } } // get the sorted marks for the specific mark extension sorted_marks = sorted_marks_map.get(filtered_mark[1]); // or create a new collection if it doesn't exist yet if (null == sorted_marks) { sorted_marks = new ArrayList<List<String[]>>(); sorted_marks_map.put(filtered_mark[1], sorted_marks); } // setup the collection of marks with the same number of // properties and create a new one if that's needed int number_of_properties = (filtered_mark.length-2)/2; if (number_of_properties < sorted_marks.size()) { marks = sorted_marks.get(number_of_properties); } else { marks = null; while (!(number_of_properties < sorted_marks.size())) { sorted_marks.add(null); } } if (null == marks) { marks = new ArrayList<String[]>(); sorted_marks.set(number_of_properties, marks); } // add the filtered mark to the collection of all marks with // the same number of properties marks.add(filtered_mark); } } Collection<String> invalid_subjects = collectSubjects(errors, prefix); // Go over the mark extensions according to their order of importance. // Marks with a broader scope (more properties) get precedence of // those with a narrower scope (less properties) and inside the // same scope level they are handled according to their order // of declaration. if (sorted_marks_map != null) { for (Map.Entry<String, ArrayList<List<String[]>>> sorted_marks_entry : sorted_marks_map.entrySet()) { String mark_block_id; String mark_extension = sorted_marks_entry.getKey(); if (null == mark_extension) { mark_block_id = PREFIX_MARK_ERROR; } else { StringBuilder buffer = new StringBuilder(PREFIX_MARK_ERROR); buffer.append(":"); buffer.append(mark_extension); mark_block_id = buffer.toString(); } HashMap<String, String> property_mark_mapping = new HashMap<String, String>(); ArrayList<List<String[]>> sorted_marks = sorted_marks_entry.getValue(); List<String[]> marks; for (int i = sorted_marks.size()-1; i >= 0; i--) { marks = sorted_marks.get(i); if (null == marks) { continue; } for (String[] filtered_mark : marks) { // check if the filtered mark contains only subjects // that are invalid boolean use_mark = true; for (int j = 2; j < filtered_mark.length; j+=2) { if (!invalid_subjects.contains(filtered_mark[j])) { use_mark = false; break; } } // If the mark can be used, register its id for // all the declared subjects. If the subject is already // bound to another value, it is simply skipped. if (use_mark) { boolean tied_to_properties = false; for (int j = 2; j < filtered_mark.length; j+=2) { if (!property_mark_mapping.containsKey(filtered_mark[j])) { property_mark_mapping.put(filtered_mark[j], filtered_mark[0]); tied_to_properties = true; } } if (tied_to_properties) { if (!template.hasBlock(mark_block_id)) { throw new MissingMarkingBlockException(mark_block_id); } template.setBlock(filtered_mark[0], mark_block_id); set_values.add(filtered_mark[0]); } } } } } } return set_values; } public void removeValidationErrors(Template template, Collection<String> subjects, String prefix) { if (null == template) { return; } if (null == subjects || 0 == subjects.size()) { return; } if (template.hasValueId(ID_ERRORS_WILDCARD)) { template.removeValue(ID_ERRORS_WILDCARD); } if (prefix != null) { ArrayList<String> prefixed_subjects = new ArrayList<String>(); for (String property : subjects) { prefixed_subjects.add(prefix+property); } subjects = prefixed_subjects; } List<String[]> filtered_errorvalues = template.getFilteredValues(TAG_ERRORS); if (filtered_errorvalues.size() > 0) { for (String[] filtered_value : filtered_errorvalues) { for (int i = 1; i < filtered_value.length; i++) { if (subjects.contains(filtered_value[i])) { template.removeValue(filtered_value[0]); break; } } } } } public void removeErrorMarkings(Template template, Collection<String> subjects, String prefix) { if (null == template) { return; } if (null == subjects || 0 == subjects.size()) { return; } if (prefix != null) { ArrayList<String> prefixed_subjects = new ArrayList<String>(); for (String property : subjects) { prefixed_subjects.add(prefix+property); } subjects = prefixed_subjects; } List<String[]> filtered_marks = template.getFilteredValues(TAG_MARK); if (filtered_marks.size() > 0) { for (String[] filtered_mark : filtered_marks) { for (int i = 2; i < filtered_mark.length; i+=2) { if (subjects.contains(filtered_mark[i])) { template.removeValue(filtered_mark[0]); break; } } } } } public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { ///CLOVER:OFF // this should never happen Logger.getLogger("com.uwyn.rife.site").severe(ExceptionUtils.getExceptionStackTrace(e)); return null; ///CLOVER:ON } } }