package org.mongodb.morphia.query; import org.mongodb.morphia.annotations.Serialized; import org.mongodb.morphia.logging.Logger; import org.mongodb.morphia.logging.MorphiaLoggerFactory; import org.mongodb.morphia.mapping.MappedClass; import org.mongodb.morphia.mapping.MappedField; import org.mongodb.morphia.mapping.Mapper; import org.mongodb.morphia.query.validation.AllOperationValidator; import org.mongodb.morphia.query.validation.DefaultTypeValidator; import org.mongodb.morphia.query.validation.DoubleTypeValidator; import org.mongodb.morphia.query.validation.EntityAnnotatedValueValidator; import org.mongodb.morphia.query.validation.EntityTypeAndIdValueValidator; import org.mongodb.morphia.query.validation.ExistsOperationValidator; import org.mongodb.morphia.query.validation.GeoWithinOperationValidator; import org.mongodb.morphia.query.validation.InOperationValidator; import org.mongodb.morphia.query.validation.IntegerTypeValidator; import org.mongodb.morphia.query.validation.KeyValueTypeValidator; import org.mongodb.morphia.query.validation.ListValueValidator; import org.mongodb.morphia.query.validation.LongTypeValidator; import org.mongodb.morphia.query.validation.ModOperationValidator; import org.mongodb.morphia.query.validation.NotInOperationValidator; import org.mongodb.morphia.query.validation.PatternValueValidator; import org.mongodb.morphia.query.validation.SizeOperationValidator; import org.mongodb.morphia.query.validation.ValidationFailure; import java.util.ArrayList; import java.util.List; import static java.lang.String.format; final class QueryValidator { private static final Logger LOG = MorphiaLoggerFactory.get(QueryValidator.class); private QueryValidator() { } /** * Validate the path, and value type, returning the mapped field for the field at the path */ static MappedField validateQuery(final Class clazz, final Mapper mapper, final StringBuilder origProp, final FilterOperator op, final Object val, final boolean validateNames, final boolean validateTypes) { MappedField mf = null; final String prop = origProp.toString(); boolean hasTranslations = false; if (!origProp.substring(0, 1).equals("$")) { final String[] parts = prop.split("\\."); if (clazz == null) { return null; } MappedClass mc = mapper.getMappedClass(clazz); //CHECKSTYLE:OFF for (int i = 0; ; ) { //CHECKSTYLE:ON final String part = parts[i]; boolean fieldIsArrayOperator = part.equals("$") || part.matches("[0-9]+"); mf = mc.getMappedField(part); //translate from java field name to stored field name if (mf == null && !fieldIsArrayOperator) { mf = mc.getMappedFieldByJavaField(part); if (validateNames && mf == null) { throw new ValidationException(format("The field '%s' could not be found in '%s' while validating - %s; if " + "you wish to continue please disable validation.", part, mc.getClazz().getName(), prop )); } hasTranslations = true; if (mf != null) { parts[i] = mf.getNameToStore(); } } i++; if (mf != null && mf.isMap()) { //skip the map key validation, and move to the next part i++; } if (i >= parts.length) { break; } if (!fieldIsArrayOperator) { //catch people trying to search/update into @Reference/@Serialized fields if (validateNames && !canQueryPast(mf)) { throw new ValidationException(format("Cannot use dot-notation past '%s' in '%s'; found while" + " validating - %s", part, mc.getClazz().getName(), prop)); } if (mf == null && (mc.isInterface() || !validateNames)) { break; } else if (mf == null) { throw new ValidationException(format("The field '%s' could not be found in '%s'", prop, mc.getClazz().getName())); } //get the next MappedClass for the next field validation mc = mapper.getMappedClass((mf.isSingleValue()) ? mf.getType() : mf.getSubClass()); } } //record new property string if there has been a translation to any part if (hasTranslations) { origProp.setLength(0); // clear existing content origProp.append(parts[0]); for (int i = 1; i < parts.length; i++) { origProp.append('.'); origProp.append(parts[i]); } } if (validateTypes && mf != null) { List<ValidationFailure> typeValidationFailures = new ArrayList<ValidationFailure>(); boolean compatibleForType = isCompatibleForOperator(mc, mf, mf.getType(), op, val, typeValidationFailures); List<ValidationFailure> subclassValidationFailures = new ArrayList<ValidationFailure>(); boolean compatibleForSubclass = isCompatibleForOperator(mc, mf, mf.getSubClass(), op, val, subclassValidationFailures); if ((mf.isSingleValue() && !compatibleForType) || mf.isMultipleValues() && !(compatibleForSubclass || compatibleForType)) { if (LOG.isWarningEnabled()) { LOG.warning(format("The type(s) for the query/update may be inconsistent; using an instance of type '%s' " + "for the field '%s.%s' which is declared as '%s'", val.getClass().getName(), mf.getDeclaringClass().getName(), mf.getJavaFieldName(), mf.getType().getName() )); typeValidationFailures.addAll(subclassValidationFailures); LOG.warning("Validation warnings: \n" + typeValidationFailures); } } } } return mf; } private static boolean canQueryPast(final MappedField mf) { return !(mf.isReference() || mf.hasAnnotation(Serialized.class)); } /*package*/ static boolean isCompatibleForOperator(final MappedClass mappedClass, final MappedField mappedField, final Class<?> type, final FilterOperator op, final Object value, final List<ValidationFailure> validationFailures) { // TODO: it's really OK to have null values? I think this is to prevent null pointers further down, // but I want to move the null check into the operations that care whether they allow nulls or not. if (value == null || type == null) { return true; } boolean validationApplied = ExistsOperationValidator.getInstance().apply(mappedField, op, value, validationFailures) || SizeOperationValidator.getInstance().apply(mappedField, op, value, validationFailures) || InOperationValidator.getInstance().apply(mappedField, op, value, validationFailures) || NotInOperationValidator.getInstance().apply(mappedField, op, value, validationFailures) || ModOperationValidator.getInstance().apply(mappedField, op, value, validationFailures) || GeoWithinOperationValidator.getInstance().apply(mappedField, op, value, validationFailures) || AllOperationValidator.getInstance().apply(mappedField, op, value, validationFailures) || KeyValueTypeValidator.getInstance().apply(type, value, validationFailures) || IntegerTypeValidator.getInstance().apply(type, value, validationFailures) || LongTypeValidator.getInstance().apply(type, value, validationFailures) || DoubleTypeValidator.getInstance().apply(type, value, validationFailures) || PatternValueValidator.getInstance().apply(type, value, validationFailures) || EntityAnnotatedValueValidator.getInstance().apply(type, value, validationFailures) || ListValueValidator.getInstance().apply(type, value, validationFailures) || EntityTypeAndIdValueValidator.getInstance() .apply(mappedClass, mappedField, value, validationFailures) || DefaultTypeValidator.getInstance().apply(type, value, validationFailures); return validationApplied && validationFailures.size() == 0; } }