/* | |
* Licensed to the Apache Software Foundation (ASF) under one | |
* or more contributor license agreements. See the NOTICE file | |
* distributed with this work for additional information | |
* regarding copyright ownership. The ASF licenses this file | |
* to you under the Apache License, Version 2.0 (the | |
* "License"); you may not use this file except in compliance | |
* with the License. You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, | |
* software distributed under the License is distributed on an | |
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
* KIND, either express or implied. See the License for the | |
* specific language governing permissions and limitations | |
* under the License. | |
*/ | |
package org.apache.myfaces.extensions.validator.crossval.strategy; | |
import java.util.ArrayList; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.MissingResourceException; | |
import java.util.logging.Level; | |
import java.lang.annotation.Annotation; | |
import javax.faces.application.FacesMessage; | |
import javax.faces.validator.ValidatorException; | |
import org.apache.myfaces.extensions.validator.crossval.storage.CrossValidationStorage; | |
import org.apache.myfaces.extensions.validator.crossval.storage.CrossValidationStorageEntry; | |
import org.apache.myfaces.extensions.validator.crossval.annotation.MessageTarget; | |
import static org.apache.myfaces.extensions.validator.crossval.annotation.MessageTarget.source; | |
import static org.apache.myfaces.extensions.validator.crossval.annotation.MessageTarget.both; | |
import static org.apache.myfaces.extensions.validator.crossval.annotation.MessageTarget.target; | |
import org.apache.myfaces.extensions.validator.util.ClassUtils; | |
import org.apache.myfaces.extensions.validator.util.ExtValUtils; | |
import org.apache.myfaces.extensions.validator.core.ExtValContext; | |
import org.apache.myfaces.extensions.validator.core.CustomInformation; | |
import org.apache.myfaces.extensions.validator.internal.UsageInformation; | |
import org.apache.myfaces.extensions.validator.internal.UsageCategory; | |
/** | |
* @since 1.x.1 | |
*/ | |
@SuppressWarnings({"unchecked"}) | |
@UsageInformation(UsageCategory.INTERNAL) | |
public abstract class AbstractCompareStrategy<A extends Annotation> extends AbstractCrossValidationStrategy | |
{ | |
protected static List<ReferencingStrategy> referencingStrategies; | |
protected Map<Object, Object> violationResultStorage = new HashMap<Object, Object>(); | |
public AbstractCompareStrategy() | |
{ | |
initReferencingStrategies(); | |
} | |
protected void initReferencingStrategies() | |
{ | |
if (referencingStrategies == null) | |
{ | |
referencingStrategies = new ArrayList<ReferencingStrategy>(); | |
String customReferencingStrategyClassName = | |
ExtValContext.getContext().getInformationProviderBean() | |
.get(CustomInformation.BASE_PACKAGE) + "ReferencingStrategy"; | |
ReferencingStrategy customReferencingStrategy = (ReferencingStrategy) ClassUtils | |
.tryToInstantiateClassForName(customReferencingStrategyClassName); | |
if (customReferencingStrategy != null) | |
{ | |
referencingStrategies.add(customReferencingStrategy); | |
} | |
referencingStrategies.add(new ELCompareStrategy()); | |
//referencingStrategies.add(new AliasCompareStrategy()); | |
referencingStrategies.add(new LocalCompareStrategy()); | |
referencingStrategies.add(new LocalPropertyChainCompareStrategy()); | |
} | |
} | |
public void processCrossValidation( | |
CrossValidationStorageEntry crossValidationStorageEntry, | |
CrossValidationStorage crossValidationStorage) throws ValidatorException | |
{ | |
//initCrossValidation is done in the CrossValidationPhaseListener | |
String[] validationTargets = getValidationTargets((A) | |
crossValidationStorageEntry.getMetaDataEntry().getValue()); | |
for (String validationTarget : validationTargets) | |
{ | |
validationTarget = validationTarget.trim(); | |
//select validation method | |
tryToValidate(crossValidationStorageEntry, crossValidationStorage, validationTarget); | |
} | |
} | |
private boolean tryToValidate( | |
CrossValidationStorageEntry crossValidationStorageEntry, | |
CrossValidationStorage crossValidationStorage, | |
String validationTarget) | |
{ | |
for (ReferencingStrategy referencingStrategy : referencingStrategies) | |
{ | |
if (referencingStrategy.evaluateReferenceAndValidate( | |
crossValidationStorageEntry, crossValidationStorage, validationTarget, this)) | |
{ | |
return true; | |
} | |
} | |
return false; | |
} | |
@SuppressWarnings({"ThrowableInstanceNeverThrown"}) | |
protected final void processTargetComponentAfterViolation( | |
CrossValidationStorageEntry entryOfSource, | |
CrossValidationStorageEntry entryOfTarget) | |
{ | |
if (!handleTargetViolation(entryOfSource, entryOfTarget)) | |
{ | |
//no target - because there is no target component - value was validated against the model | |
if(entryOfTarget == null) | |
{ | |
processTargetComponentAsSourceComponentAfterViolation(entryOfSource); | |
return; | |
} | |
return; | |
} | |
//get validation error messages for the target component | |
String summary = getErrorMessageSummary((A)entryOfSource.getMetaDataEntry().getValue(), true); | |
String details = getErrorMessageDetail((A)entryOfSource.getMetaDataEntry().getValue(), true); | |
//validation target isn't bound to a component withing the current page | |
//(see validateFoundEntry, tryToValidateLocally and tryToValidateBindingOnly) | |
if (entryOfTarget == null) | |
{ | |
entryOfTarget = entryOfSource; | |
} | |
FacesMessage message; | |
if (entryOfTarget.getMetaDataEntry() != null) | |
{ | |
message = getTargetComponentErrorMessage((A)entryOfTarget.getMetaDataEntry().getValue(), summary, details); | |
} | |
else | |
{ | |
//TODO document possible side effects | |
//due to a missing target annotation (see: tryToValidateLocally) | |
message = getTargetComponentErrorMessage((A)entryOfSource.getMetaDataEntry().getValue(), summary, details); | |
} | |
if ((message.getSummary() != null || message.getDetail() != null) && | |
entryOfSource.getClientId() != null && !entryOfSource.getClientId().equals(entryOfTarget.getClientId())) | |
{ | |
ValidatorException validatorException = new ValidatorException(message); | |
boolean isSourceMetaDataUsed = false; | |
if(entryOfTarget.getMetaDataEntry() == null) | |
{ | |
prepareTargetMetaDataForSeverityAwareInterception(entryOfSource, entryOfTarget); | |
isSourceMetaDataUsed = true; | |
} | |
if(ExtValUtils.executeAfterThrowingInterceptors( | |
entryOfTarget.getComponent(), entryOfTarget.getMetaDataEntry(), | |
entryOfTarget.getConvertedObject(), validatorException, this)) | |
{ | |
ExtValUtils.tryToAddViolationMessageForComponentId(entryOfTarget.getClientId(), | |
ExtValUtils.convertFacesMessage(validatorException.getFacesMessage())); | |
} | |
if(isSourceMetaDataUsed) | |
{ | |
resetTargetMetaData(entryOfTarget); | |
} | |
} | |
} | |
private void prepareTargetMetaDataForSeverityAwareInterception( | |
CrossValidationStorageEntry entryOfSource, CrossValidationStorageEntry entryOfTarget) | |
{ | |
entryOfTarget.setMetaDataEntry(entryOfSource.getMetaDataEntry()); | |
} | |
private void resetTargetMetaData(CrossValidationStorageEntry entryOfTarget) | |
{ | |
entryOfTarget.setMetaDataEntry(null); | |
} | |
@SuppressWarnings({"ThrowableInstanceNeverThrown"}) | |
private void processTargetComponentAsSourceComponentAfterViolation(CrossValidationStorageEntry entryOfSource) | |
{ | |
//get validation error messages for the current component | |
String summary = getReverseErrorMessageSummary((A)entryOfSource.getMetaDataEntry().getValue()); | |
String details = getReverseErrorMessageDetail((A)entryOfSource.getMetaDataEntry().getValue()); | |
FacesMessage message = getSourceComponentErrorMessage( | |
(A)entryOfSource.getMetaDataEntry().getValue(), summary, details); | |
if (message.getSummary() != null || message.getDetail() != null) | |
{ | |
//TODO | |
if(ExtValUtils.executeAfterThrowingInterceptors(entryOfSource.getComponent(), | |
entryOfSource.getMetaDataEntry(), | |
entryOfSource.getConvertedObject(), | |
new ValidatorException(message), | |
this)) | |
{ | |
ExtValUtils.tryToThrowValidatorExceptionForComponent(entryOfSource.getComponent(), message, null); | |
} | |
} | |
else | |
{ | |
//TODO logging | |
} | |
} | |
protected final void processSourceComponentAfterViolation(CrossValidationStorageEntry entryOfSource) | |
{ | |
if (handleSourceViolation(entryOfSource)) | |
{ | |
//get validation error messages for the current component | |
String summary = getErrorMessageSummary((A)entryOfSource.getMetaDataEntry().getValue(), false); | |
String details = getErrorMessageDetail((A)entryOfSource.getMetaDataEntry().getValue(), false); | |
FacesMessage message = getSourceComponentErrorMessage( | |
(A)entryOfSource.getMetaDataEntry().getValue(), summary, details); | |
if (message.getSummary() != null || message.getDetail() != null) | |
{ | |
//TODO | |
ExtValUtils.tryToThrowValidatorExceptionForComponent(entryOfSource.getComponent(), message, null); | |
} | |
} | |
//just throw a new message - the error message is at the target | |
ExtValUtils.tryToThrowValidatorExceptionForComponent( | |
entryOfSource.getComponent(), new FacesMessage(FacesMessage.SEVERITY_ERROR, null, null), null); | |
} | |
protected FacesMessage getSourceComponentErrorMessage(A annotation, String summary, String detail) | |
{ | |
return ExtValUtils.createFacesMessage(summary, detail); | |
} | |
protected FacesMessage getTargetComponentErrorMessage(A foundAnnotation, String summary, String detail) | |
{ | |
return ExtValUtils.createFacesMessage(summary, detail); | |
} | |
protected String getErrorMessageSummary(A annotation, boolean isTargetComponent) | |
{ | |
return resolveMessage(getValidationErrorMsgKey(annotation, isTargetComponent)); | |
} | |
protected String getErrorMessageDetail(A annotation, boolean isTargetComponent) | |
{ | |
try | |
{ | |
String key = getValidationErrorMsgKey(annotation, isTargetComponent); | |
return (key != null) ? resolveMessage(key + DETAIL_MESSAGE_KEY_POSTFIX) : null; | |
} | |
catch (MissingResourceException e) | |
{ | |
logger.log(Level.WARNING, "couldn't find key " + getValidationErrorMsgKey(annotation, isTargetComponent) | |
+ DETAIL_MESSAGE_KEY_POSTFIX, e); | |
} | |
return null; | |
} | |
protected final String getValidationErrorMsgKey(Annotation annotation) | |
{ | |
return getValidationErrorMsgKey((A)annotation, false); | |
} | |
protected boolean handleTargetViolation( | |
CrossValidationStorageEntry entryOfSource, | |
CrossValidationStorageEntry entryOfTarget) | |
{ | |
return entryOfTarget != null && entryOfTarget.getComponent() != null; | |
} | |
@Deprecated | |
protected boolean handleSourceViolation(CrossValidationStorageEntry entryOfSource) | |
{ | |
return useSourceComponentToDisplayErrorMsg(entryOfSource); | |
} | |
protected boolean useSourceComponentToDisplayErrorMsg(CrossValidationStorageEntry entryOfSource) | |
{ | |
MessageTarget messageTarget = getMessageTarget((A)entryOfSource.getMetaDataEntry().getValue()); | |
return messageTarget.equals(source) || messageTarget.equals(both); | |
} | |
protected boolean useTargetComponentToDisplayErrorMsg(CrossValidationStorageEntry entryOfSource) | |
{ | |
MessageTarget messageTarget = getMessageTarget((A)entryOfSource.getMetaDataEntry().getValue()); | |
return messageTarget.equals(target) || messageTarget.equals(both); | |
} | |
protected abstract MessageTarget getMessageTarget(A annotation); | |
/* | |
* no target component (validation against the model) -> get reverse message for source component | |
*/ | |
protected String getReverseErrorMessageSummary(A annotation) | |
{ | |
//if the message is neutral | |
return getErrorMessageSummary(annotation, true); | |
} | |
/* | |
* no target component (validation against the model) -> get reverse message for source component | |
*/ | |
protected String getReverseErrorMessageDetail(A annotation) | |
{ | |
//if the message is neutral | |
return getErrorMessageDetail(annotation, true); | |
} | |
/* | |
* abstract methods | |
*/ | |
protected abstract String getValidationErrorMsgKey(A annotation, boolean isTargetComponent); | |
/* | |
* implements the specific validation logic | |
*/ | |
public abstract boolean isViolation(Object object1, Object object2, A annotation); | |
/* | |
* returns the referenced validation targets of the annotation | |
* e.g. @DateIs(type = DateIsType.before, value = "finalExam") | |
* -> method returns an array with one value ("finalExam") | |
*/ | |
public abstract String[] getValidationTargets(A annotation); | |
} |