package jeffaschenk.commons.exceptions; import org.springframework.beans.PropertyAccessorUtils; import org.springframework.util.StringUtils; import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError; import java.util.*; /** * The Errors object will hold the errors that occur any where in upstream or downstream layers. * * @author jeffaschenk@gmail.com * @version $Id: $ */ public class ErrorsImpl implements Errors { private String classSimpleName; private List<FieldError> fieldErrors = new ArrayList<FieldError>(); private List<ObjectError> globalErrors = new ArrayList<ObjectError>(); private List<FieldError> warnings = new ArrayList<FieldError>(); private String nestedPath = ""; private final Stack<String> nestedPathStack = new Stack<String>(); private String currentContextName; private final Stack<String> contextNameStack = new Stack<String>(); private Map<Class<?>, Map<List<String>, List<List<Object>>>> classUniqueConstraintUpdates; private Map<Class<?>, Map<List<String>, Map<String, FieldError>>> rectifiableUniqeConstraintErrors; private Map<List<String>, Set<String>> updatedConstraintFieldsGuids; /** * Default Constructor */ public ErrorsImpl() { this.classSimpleName = ""; } /** * Errors Errors constructor. Must pass in Asset object to validate against. * * @param classSimpleName Asset errors occur against */ public ErrorsImpl(String classSimpleName) { this.classSimpleName = classSimpleName; } /** * {@inheritDoc} * <p/> * Add all errors from another Errors class */ public void addAllErrors(org.springframework.validation.Errors errors) { this.fieldErrors.addAll(errors.getFieldErrors()); this.globalErrors.addAll(errors.getGlobalErrors()); } /** * Actually set the nested path. * Delegated to by setNestedPath and pushNestedPath. * <p/> * Note: Not yet known what to use the path for. Possibly if the Asset has * second class Asset contained inside it. * * @param nestedPath Path */ protected void doSetNestedPath(String nestedPath) { if (nestedPath == null) { nestedPath = ""; } nestedPath = PropertyAccessorUtils.canonicalPropertyName(nestedPath); if (nestedPath.length() > 0 && !nestedPath.endsWith(NESTED_PATH_SEPARATOR)) { nestedPath += NESTED_PATH_SEPARATOR; } this.nestedPath = nestedPath; } /** * Transform the given field into its full path, * regarding the nested path of this instance. * * @param field a {@link java.lang.String} object. * @return {@link java.lang.String} object. */ protected String fixedField(String field) { if (StringUtils.hasLength(field)) { return getNestedPath() + PropertyAccessorUtils.canonicalPropertyName(field); } else { String path = getNestedPath(); return (path.endsWith(org.springframework.validation.Errors.NESTED_PATH_SEPARATOR) ? path.substring(0, path.length() - NESTED_PATH_SEPARATOR.length()) : path); } } /** * Get all errors including both Field and Global Errors * <p/> * return List of all Field and Global Errors * * @return {@link java.util.List} object. */ public List<ObjectError> getAllErrors() { List<ObjectError> objectErrorsList = new ArrayList<ObjectError>(); objectErrorsList.addAll(this.fieldErrors); objectErrorsList.addAll(this.globalErrors); return objectErrorsList; } /** * Get count of all errors * * @return Get Error Count */ public int getErrorCount() { return this.fieldErrors.size() + this.globalErrors.size(); } /** * Get First Field Error in the List * * @return Return first Field Error */ public FieldError getFieldError() { if (this.fieldErrors.size() > 0) { return this.fieldErrors.iterator().next(); } else return null; } /** * {@inheritDoc} * <p/> * Get first FieldError for a given field value */ public FieldError getFieldError(String field) { if (this.fieldErrors.size() > 0) { Iterator<FieldError> fieldErrorsIter = this.fieldErrors.iterator(); while (fieldErrorsIter.hasNext()) { FieldError curFieldError = fieldErrorsIter.next(); if (curFieldError.getField().equals(field)) return curFieldError; } return null; } else return null; } /** * Get the number of Field Errors * * @return Number of field errors */ public int getFieldErrorCount() { return this.fieldErrors.size(); } /** * {@inheritDoc} * <p/> * Get the number of errors for a given field */ public int getFieldErrorCount(String field) { return getFieldErrors(field).size(); } /** * Get all Field Errors * * @return Return a list of all field errors */ public List<FieldError> getFieldErrors() { return this.fieldErrors; } /** * {@inheritDoc} * <p/> * Get all Field Errors for a given field */ public List<FieldError> getFieldErrors(String field) { List<FieldError> curFieldErrors = new ArrayList<FieldError>(); Iterator<FieldError> fieldErrorsIter = this.fieldErrors.iterator(); while (fieldErrorsIter.hasNext()) { FieldError curFieldError = fieldErrorsIter.next(); if (curFieldError.getField().equals(field)) curFieldErrors.add(curFieldError); } return curFieldErrors; } /** * {@inheritDoc} * <p/> * Get Class/Object Field Error is associated with */ public Class<? extends String> getFieldType(String field) { if (this.fieldErrors.size() > 0) { Iterator<FieldError> fieldErrorsIter = this.fieldErrors.iterator(); while (fieldErrorsIter.hasNext()) { FieldError curFieldError = fieldErrorsIter.next(); if (curFieldError.getField().equals(field)) return curFieldError.getObjectName().getClass(); } return null; } else return null; } /** * {@inheritDoc} * <p/> * Do not know use as of yet */ @Deprecated public Object getFieldValue(String field) { FieldError fe = getFieldError(field); if (fe == null) return null; else return fixedField(field); } /** * Get First Global Error (Object Error) * * @return Get First Global Error (Object Error) */ public ObjectError getGlobalError() { if (this.globalErrors.size() > 0) { return this.globalErrors.iterator().next(); } else return null; } /** * Get count of all Global Errors * * @return Count of all Global Errors */ public int getGlobalErrorCount() { return this.globalErrors.size(); } /** * Get a list of all Global Errors * * @return list of all Global Errors */ public List<ObjectError> getGlobalErrors() { return this.globalErrors; } /** * Get Current nested Path * * @return Current nested path */ public String getNestedPath() { return this.nestedPath; } /** * Get Asset Type * * @return Get Asset Type */ public String getObjectName() { return classSimpleName; } /** * Do Errors exist * * @return Do Errors exist */ public boolean hasErrors() { return !getAllErrors().isEmpty(); } /** * Do Field Errors exist * * @return Do Field Errors Exist */ public boolean hasFieldErrors() { return (this.fieldErrors.size() > 0); } /** * {@inheritDoc} * <p/> * Do Field Errors exist for a given field */ public boolean hasFieldErrors(String field) { return this.getFieldErrors(field).size() > 0; } /** * Do Global Errors exist * * @return Do Global Errors exist */ public boolean hasGlobalErrors() { return (this.globalErrors.size() > 0); } /* * (non-Javadoc) * @see com.sungard.cmdb.CMDBErrors#popContextName() */ /** * {@inheritDoc} */ @Override public void popContextName() { try { currentContextName = contextNameStack.pop(); } catch (EmptyStackException ex) { throw new IllegalStateException("Cannot pop context name: empty stack"); } } /* * (non-Javadoc) * @see com.sungard.cmdb.CMDBErrors#pushContextName(java.lang.String) */ /** * {@inheritDoc} */ @Override public void pushContextName(String contextName) { contextNameStack.push(currentContextName); currentContextName = contextName; } /* * (non-Javadoc) * @see com.sungard.cmdb.CMDBErrors#getCurrentContextName() */ /** * {@inheritDoc} */ @Override public String getCurrentContextName() { return currentContextName; } /** * Pop NestedPath * * @throws java.lang.IllegalStateException * if any. */ public void popNestedPath() throws IllegalStateException { try { String formerNestedPath = this.nestedPathStack.pop(); doSetNestedPath(formerNestedPath); } catch (EmptyStackException ex) { throw new IllegalStateException("Cannot pop nested path: no nested path on stack"); } } /** * {@inheritDoc} * <p/> * Push a subpath on current path */ public void pushNestedPath(String subPath) { this.nestedPathStack.push(getNestedPath()); doSetNestedPath(getNestedPath() + subPath); } /** * {@inheritDoc} * <p/> * Reject: Add Global Error */ public void reject(String errorCode) { reject(errorCode, null, null); } /** * {@inheritDoc} * <p/> * Reject: Add Global Error */ public void reject(String errorCode, Object[] errorArgs, String defaultMessage) { //ObjectError(String objectName, String[] codes, Object[] arguments, String defaultMessage) String[] codes = new String[]{errorCode}; ObjectError objectError = new ObjectError(classSimpleName, codes, errorArgs, defaultMessage); this.globalErrors.add(objectError); } /** * {@inheritDoc} * <p/> * Reject: Add Global Error */ public void reject(String errorCode, String defaultMessage) { reject(errorCode, null, defaultMessage); } /** * {@inheritDoc} * <p/> * Reject: Add Field Error */ public void rejectValue(String field, String errorCode) { rejectValue(field, errorCode, null, null); } /** * {@inheritDoc} * <p/> * Reject: Add Field Error */ public void rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage) { //Register a field error for the specified field of the current object (respecting the current nested path, if any), using the given error description. //FieldError(String objectName, String field, Object rejectedValue, boolean bindingFailure, String[] codes, Object[] arguments, String defaultMessage) String fixedField = fixedField(field); String[] codes = new String[]{errorCode}; FieldError fe = new FieldError(classSimpleName, fixedField, null, false, codes, errorArgs, defaultMessage); this.fieldErrors.add(fe); } /** * {@inheritDoc} * <p/> * Reject: Add Field Error */ public void rejectValue(String field, String errorCode, String defaultMessage) { rejectValue(field, errorCode, null, defaultMessage); } /** * {@inheritDoc} * <p/> * Set Path */ public void setNestedPath(String nestedPath) { doSetNestedPath(nestedPath); this.nestedPathStack.clear(); } /** * {@inheritDoc} */ @Override public List<FieldError> getWarnings() { return warnings; } /** * {@inheritDoc} */ @Override public boolean hasWarnings() { return warnings.size() > 0; } /** * {@inheritDoc} */ @Override public void warn(String field, String errorCode) { warn(field, errorCode, null); } /** * {@inheritDoc} */ @Override public void warn(String field, String errorCode, String defaultMessage) { String fixedField = fixedField(field); String[] codes = new String[]{errorCode}; FieldError warning = new FieldError(classSimpleName, fixedField, null, false, codes, null, defaultMessage); warnings.add(warning); } /** * {@inheritDoc} */ @Override public Map<Class<?>, Map<List<String>, List<List<Object>>>> getClassUniqueConstraintUpdates() { if (classUniqueConstraintUpdates == null) { classUniqueConstraintUpdates = new HashMap<Class<?>, Map<List<String>, List<List<Object>>>>(); } return classUniqueConstraintUpdates; } /** * {@inheritDoc} */ @Override public Map<List<String>, Set<String>> getUpdatedConstraintFieldsGuids() { if (updatedConstraintFieldsGuids == null) { updatedConstraintFieldsGuids = new HashMap<List<String>, Set<String>>(); } return updatedConstraintFieldsGuids; } private Map<Class<?>, Map<List<String>, Map<String, FieldError>>> getRectifiableUniqueConstraintErrors() { if (rectifiableUniqeConstraintErrors == null) { rectifiableUniqeConstraintErrors = new HashMap<Class<?>, Map<List<String>, Map<String, FieldError>>>(); } return rectifiableUniqeConstraintErrors; } /** * {@inheritDoc} */ @Override public void addRectifiableUniqueConstraintViolation( Class<?> targetEntity, List<String> uniqueConstraintFields, String guid, String errorCode, Object[] values, String defaultMessage) { Map<List<String>, Map<String, FieldError>> uniqueConstraintFieldsGuidFieldErrors = getRectifiableUniqueConstraintErrors().get(targetEntity); if (uniqueConstraintFieldsGuidFieldErrors == null) { uniqueConstraintFieldsGuidFieldErrors = new HashMap<List<String>, Map<String, FieldError>>(); rectifiableUniqeConstraintErrors.put(targetEntity, uniqueConstraintFieldsGuidFieldErrors); } Map<String, FieldError> guidFieldError = uniqueConstraintFieldsGuidFieldErrors.get(uniqueConstraintFields); if (guidFieldError == null) { guidFieldError = new HashMap<String, FieldError>(); uniqueConstraintFieldsGuidFieldErrors.put(uniqueConstraintFields, guidFieldError); } //add a normal field error String[] codes = new String[]{errorCode}; FieldError fieldError = new FieldError(classSimpleName, fixedField(null), null, false, codes, values, defaultMessage); fieldErrors.add(fieldError); //map the fieldError to a key so it can subsequently be rectified guidFieldError.put(guid, fieldError); } /** * {@inheritDoc} */ @Override public boolean rectifyUniqueConstraintViolation( Class<?> targetEntity, List<String> uniqueConstraintFields, String guid) { boolean rectified = false; Map<List<String>, Map<String, FieldError>> uniqueConstraintFieldsGuidFieldErrors = getRectifiableUniqueConstraintErrors().get(targetEntity); if (uniqueConstraintFieldsGuidFieldErrors != null) { Map<String, FieldError> guidFieldErrors = uniqueConstraintFieldsGuidFieldErrors.get(uniqueConstraintFields); if (guidFieldErrors != null) { FieldError fieldError = guidFieldErrors.get(guid); if (fieldError != null) { rectified = fieldErrors.remove(fieldError); } } } return rectified; } }