Skip to content

Commit

Permalink
make the path validation smarter to avoid and hopefully eliminate the…
Browse files Browse the repository at this point in the history
… need for disabling validation.

fixed #379
  • Loading branch information
Justin Lee committed Oct 28, 2016
1 parent d0e5329 commit 0e0e235
Show file tree
Hide file tree
Showing 10 changed files with 373 additions and 40 deletions.
10 changes: 1 addition & 9 deletions config/clirr-exclude.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,4 @@
differenceTypes: []

packages:
- net.sf.cglib.asm
- net.sf.cglib.asm.signature
- net.sf.cglib.beans
- net.sf.cglib.core
- net.sf.cglib.proxy
- net.sf.cglib.reflect
- net.sf.cglib.transform
- net.sf.cglib.transform.impl
- net.sf.cglib.util
- com.mongodb.*
12 changes: 1 addition & 11 deletions morphia/src/main/java/org/mongodb/morphia/IndexHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static org.mongodb.morphia.AnnotationBuilder.toMap;
import static org.mongodb.morphia.MorphiaUtils.join;
import static org.mongodb.morphia.utils.IndexType.fromValue;

final class IndexHelper {
Expand All @@ -66,17 +67,6 @@ final class IndexHelper {
this.database = database;
}

private static String join(final List<String> path, final char delimiter) {
StringBuilder builder = new StringBuilder();
for (String element : path) {
if (builder.length() != 0) {
builder.append(delimiter);
}
builder.append(element);
}
return builder.toString();
}

private void calculateWeights(final Index index, final com.mongodb.client.model.IndexOptions indexOptions) {
Document weights = new Document();
for (Field field : index.fields()) {
Expand Down
48 changes: 48 additions & 0 deletions morphia/src/main/java/org/mongodb/morphia/MorphiaUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2016 MongoDB, Inc.
*
* Licensed 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.mongodb.morphia;

import java.util.List;

/**
* This class provides a set of utilities for use in Morphia. All these methods should be considered internal only and should not be
* used in application code.
*
* @since 1.3
*/
public final class MorphiaUtils {
private MorphiaUtils() {
}

/**
* Joins strings with the given delimiter
*
* @param strings the strings to join
* @param delimiter the delimiter
* @return the joined string
*/
public static String join(final List<String> strings, final char delimiter) {
StringBuilder builder = new StringBuilder();
for (String element : strings) {
if (builder.length() != 0) {
builder.append(delimiter);
}
builder.append(element);
}
return builder.toString();
}
}
155 changes: 155 additions & 0 deletions morphia/src/main/java/org/mongodb/morphia/PathTarget.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* Copyright 2016 MongoDB, Inc.
*
* Licensed 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.mongodb.morphia;

import org.mongodb.morphia.mapping.MappedClass;
import org.mongodb.morphia.mapping.MappedField;
import org.mongodb.morphia.mapping.Mapper;
import org.mongodb.morphia.query.ValidationException;

import java.util.Iterator;
import java.util.List;

import static java.lang.String.format;
import static java.util.Arrays.asList;
import static org.mongodb.morphia.MorphiaUtils.join;

/**
* This is an internal class and is subject to change or removal.
*
* @since 1.3
*/
public class PathTarget {
private final List<String> segments;
private boolean validateNames = true;
private int position;
private Mapper mapper;
private MappedClass context;
private MappedClass root;
private MappedField target;
private boolean resolved = false;

/**
* Creates a resolution context for the given root and path.
*
* @param mapper mapper
* @param root root
* @param path path
*/
public PathTarget(final Mapper mapper, final MappedClass root, final String path) {
this.root = root;
segments = asList(path.split("\\."));
this.mapper = mapper;
}

/**
* Disables validation of path segments.
*/
public void disableValidation() {
resolved = false;
validateNames = false;
}

private boolean hasNext() {
return position < segments.size();
}

/**
* Returns the translated path for this context. If validation is disabled, that path could be the same as the initial value.
*
* @return the translated path
*/
public String translatedPath() {
if (!resolved) {
resolve();
}
return join(segments, '.');
}

/**
* Returns the MappedField found at the end of a path. May be null if the path is invalid and validation is disabled.
*
* @return the field
*/
public MappedField getTarget() {
if (!resolved) {
resolve();
}
return target;
}

String next() {
return segments.get(position++);
}

private void resolve() {
context = this.root;
position = 0;
MappedField field = null;
while (hasNext()) {
String segment = next();

if (segment.equals("$") || segment.matches("[0-9]+")) { // array operator
segment = next();
}
field = resolveField(segment);

if (field != null) {
if (!field.isMap()) {
translate(field.getNameToStore());
} else {
next(); // consume the map key segment
}
} else {
if (validateNames) {
throw new ValidationException(format("Could not resolve path '%s' against '%s'.", join(segments, '.'),
root.getClazz().getName()));
}
}
}
target = field;
resolved = true;
}

private void translate(final String nameToStore) {
segments.set(position - 1, nameToStore);
}

private MappedField resolveField(final String segment) {
MappedField mf = context.getMappedField(segment);
if (mf == null) {
mf = context.getMappedFieldByJavaField(segment);
}
if (mf == null) {
Iterator<MappedClass> subTypes = mapper.getSubTypes(context).iterator();
while (mf == null && subTypes.hasNext()) {
context = subTypes.next();
mf = resolveField(segment);
}
}

if (mf != null) {
context = mapper.getMappedClass(mf.getSubClass() != null ? mf.getSubClass() : mf.getConcreteType());
}
return mf;
}

@Override
public String toString() {
return String.format("PathTarget{root=%s, segments=%s, target=%s}", root.getClazz().getSimpleName(), segments, target);
}
}
36 changes: 17 additions & 19 deletions morphia/src/main/java/org/mongodb/morphia/query/UpdateOpsImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import org.mongodb.morphia.PathTarget;
import org.mongodb.morphia.mapping.MappedField;
import org.mongodb.morphia.mapping.Mapper;

Expand All @@ -13,7 +14,6 @@
import java.util.Map;

import static java.util.Collections.singletonList;
import static org.mongodb.morphia.query.QueryValidator.validateQuery;


/**
Expand All @@ -25,7 +25,6 @@ public class UpdateOpsImpl<T> implements UpdateOperations<T> {
private final Class<T> clazz;
private Map<String, Map<String, Object>> ops = new HashMap<String, Map<String, Object>>();
private boolean validateNames = true;
private boolean validateTypes = true;
private boolean isolated;

/**
Expand Down Expand Up @@ -113,12 +112,14 @@ public UpdateOperations<T> push(final String field, final List<?> values, final
throw new QueryException("Values cannot be null or empty.");
}

StringBuilder fieldName = new StringBuilder(field);
MappedField mf = validate(values, fieldName);
PathTarget pathTarget = new PathTarget(mapper, mapper.getMappedClass(clazz), field);
if (!validateNames) {
pathTarget.disableValidation();
}

BasicDBObject dbObject = new BasicDBObject(UpdateOperator.EACH.val(), mapper.toMongoObject(mf, null, values));
BasicDBObject dbObject = new BasicDBObject(UpdateOperator.EACH.val(), mapper.toMongoObject(pathTarget.getTarget(), null, values));
options.update(dbObject);
addOperation(UpdateOperator.PUSH, fieldName, dbObject);
addOperation(UpdateOperator.PUSH, pathTarget.translatedPath(), dbObject);

return this;
}
Expand All @@ -143,14 +144,12 @@ public UpdateOperations<T> dec(final String field, final Number value) {
@Override
public UpdateOperations<T> disableValidation() {
validateNames = false;
validateTypes = false;
return this;
}

@Override
public UpdateOperations<T> enableValidation() {
validateNames = true;
validateTypes = true;
return this;
}

Expand Down Expand Up @@ -261,6 +260,7 @@ public void setOps(final DBObject ops) {
/**
* @return true if isolated
*/
@Override
public boolean isIsolated() {
return isolated;
}
Expand All @@ -272,8 +272,11 @@ protected void add(final UpdateOperator op, final String f, final Object value,
}

Object val = value;
StringBuilder fieldName = new StringBuilder(f);
MappedField mf = validate(val, fieldName);
PathTarget pathTarget = new PathTarget(mapper, mapper.getMappedClass(clazz), f);
if (!validateNames) {
pathTarget.disableValidation();
}
MappedField mf = pathTarget.getTarget();

if (convert) {
if (UpdateOperator.PULL_ALL.equals(op) && value instanceof List) {
Expand All @@ -288,22 +291,16 @@ protected void add(final UpdateOperator op, final String f, final Object value,
val = new BasicDBObject(UpdateOperator.EACH.val(), val);
}

addOperation(op, fieldName, val);
addOperation(op, pathTarget.translatedPath(), val);
}

private void addOperation(final UpdateOperator op, final StringBuilder fieldName, final Object val) {
private void addOperation(final UpdateOperator op, final String fieldName, final Object val) {
final String opString = op.val();

if (!ops.containsKey(opString)) {
ops.put(opString, new LinkedHashMap<String, Object>());
}
ops.get(opString).put(fieldName.toString(), val);
}

private MappedField validate(final Object val, final StringBuilder fieldName) {
return validateNames || validateTypes
? validateQuery(clazz, mapper, fieldName, FilterOperator.EQUAL, val, validateNames, validateTypes)
: null;
ops.get(opString).put(fieldName, val);
}

protected UpdateOperations<T> remove(final String fieldExpr, final boolean firstNotLast) {
Expand All @@ -319,4 +316,5 @@ protected List<Object> toDBObjList(final MappedField mf, final List<?> values) {

return list;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public void calculateBadKeys() {
.type(IndexType.DESC));
try {
indexHelper.calculateKeys(mappedClass, index);
fail("Validation should have errored on the bad key");
fail("Validation should have failed on the bad key");
} catch (MappingException e) {
// all good
}
Expand Down
Loading

0 comments on commit 0e0e235

Please sign in to comment.