diff --git a/src/main/java/com/avaje/ebean/EbeanServer.java b/src/main/java/com/avaje/ebean/EbeanServer.java index 4f582c591..ae17c78a3 100644 --- a/src/main/java/com/avaje/ebean/EbeanServer.java +++ b/src/main/java/com/avaje/ebean/EbeanServer.java @@ -1886,8 +1886,8 @@ public interface EbeanServer { * * } * - * @see com.avaje.ebean.text.PathProperties - * @see Query#apply(com.avaje.ebean.text.PathProperties) + * @see FetchPath + * @see Query#apply(FetchPath) */ JsonContext json(); diff --git a/src/main/java/com/avaje/ebean/ExpressionList.java b/src/main/java/com/avaje/ebean/ExpressionList.java index 309190b4d..316efbdad 100644 --- a/src/main/java/com/avaje/ebean/ExpressionList.java +++ b/src/main/java/com/avaje/ebean/ExpressionList.java @@ -1,6 +1,5 @@ package com.avaje.ebean; -import com.avaje.ebean.text.PathProperties; import org.jetbrains.annotations.Nullable; import javax.persistence.NonUniqueResultException; @@ -95,7 +94,7 @@ public interface ExpressionList extends Serializable { /** * Apply the path properties to the query replacing the select and fetch clauses. */ - Query apply(PathProperties pathProperties); + Query apply(FetchPath fetchPath); /** * Perform an 'As of' query using history tables to return the object graph diff --git a/src/main/java/com/avaje/ebean/FetchPath.java b/src/main/java/com/avaje/ebean/FetchPath.java new file mode 100644 index 000000000..71aa83a09 --- /dev/null +++ b/src/main/java/com/avaje/ebean/FetchPath.java @@ -0,0 +1,26 @@ +package com.avaje.ebean; + +import java.util.Set; + +/** + * Provides paths and properties for an object graph that can be used to control what parts of the object graph + * is fetching (select and fetch clauses) and also can be used to control JSON marshalling (what parts of the object + * graph are included in the JSON). + */ +public interface FetchPath { + + /** + * Return true if the path is included in this FetchPath. + */ + boolean hasPath(String path); + + /** + * Return the properties at the given path. + */ + Set getProperties(String path); + + /** + * Apply the fetch path to the query. + */ + void apply(Query query); +} diff --git a/src/main/java/com/avaje/ebean/Model.java b/src/main/java/com/avaje/ebean/Model.java index 50dffb060..2a36dbe18 100644 --- a/src/main/java/com/avaje/ebean/Model.java +++ b/src/main/java/com/avaje/ebean/Model.java @@ -1,7 +1,6 @@ package com.avaje.ebean; import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebean.text.PathProperties; import com.avaje.ebean.util.ClassUtil; import org.jetbrains.annotations.Nullable; @@ -588,10 +587,10 @@ public abstract class Model { /** * Creates a query applying the path properties to set the select and fetch clauses. *

- * Equivalent to {@link Query#apply(com.avaje.ebean.text.PathProperties)} + * Equivalent to {@link Query#apply(FetchPath)} */ - public Query apply(PathProperties pathProperties) { - return db().find(type).apply(pathProperties); + public Query apply(FetchPath fetchPath) { + return db().find(type).apply(fetchPath); } /** diff --git a/src/main/java/com/avaje/ebean/Query.java b/src/main/java/com/avaje/ebean/Query.java index 074ced3c9..0db48945f 100644 --- a/src/main/java/com/avaje/ebean/Query.java +++ b/src/main/java/com/avaje/ebean/Query.java @@ -1,6 +1,5 @@ package com.avaje.ebean; -import com.avaje.ebean.text.PathProperties; import org.jetbrains.annotations.Nullable; import javax.persistence.NonUniqueResultException; @@ -522,10 +521,10 @@ public interface Query extends Serializable { /** * Apply the path properties replacing the select and fetch clauses. *

- * This is typically used when the PathProperties is applied to both the query and the JSON output. + * This is typically used when the FetchPath is applied to both the query and the JSON output. *

*/ - Query apply(PathProperties pathProperties); + Query apply(FetchPath fetchPath); /** * Execute the query returning the list of Id's. diff --git a/src/main/java/com/avaje/ebean/text/PathProperties.java b/src/main/java/com/avaje/ebean/text/PathProperties.java index 5dac251b4..5892027ef 100644 --- a/src/main/java/com/avaje/ebean/text/PathProperties.java +++ b/src/main/java/com/avaje/ebean/text/PathProperties.java @@ -1,14 +1,16 @@ package com.avaje.ebean.text; +import com.avaje.ebean.FetchPath; +import com.avaje.ebean.Query; +import com.avaje.ebeaninternal.server.query.SplitName; + import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; -import java.util.Set; import java.util.Map.Entry; - -import com.avaje.ebean.Query; +import java.util.Set; /** * This is a Tree like structure of paths and properties that can be used for @@ -20,7 +22,7 @@ import com.avaje.ebean.Query; * render (JAX-RS JSON / XML). *

*/ -public class PathProperties { +public class PathProperties implements FetchPath { private final Map pathMap; @@ -44,39 +46,6 @@ public class PathProperties { this.pathMap.put(null, rootProps); } - /** - * Construct for creating copy. - */ - private PathProperties(PathProperties orig) { - this.rootProps = orig.rootProps.copy(this); - this.pathMap = new LinkedHashMap(orig.pathMap.size()); - Set> entrySet = orig.pathMap.entrySet(); - for (Entry e : entrySet) { - pathMap.put(e.getKey(), e.getValue().copy(this)); - } - } - - /** - * Create a copy of this instance so that it can be modified. - *

- * For example, you may want to create a copy to add extra properties to a - * path so that they are fetching in a ORM query but perhaps not rendered by - * default. That is, use a PathProperties for JSON or XML rendering, but - * create a copy, add some extra properties and then use that copy to define - * an ORM query. - *

- */ - public PathProperties copy() { - return new PathProperties(this); - } - - /** - * Return true if there are no paths defined. - */ - public boolean isEmpty() { - return pathMap.isEmpty(); - } - public String toString() { return pathMap.toString(); } @@ -84,6 +53,7 @@ public class PathProperties { /** * Return true if the path is defined and has properties. */ + @Override public boolean hasPath(String path) { Props props = pathMap.get(path); return props != null && !props.isEmpty(); @@ -92,40 +62,38 @@ public class PathProperties { /** * Get the properties for a given path. */ - public LinkedHashSet get(String path) { + @Override + public Set getProperties(String path) { Props props = pathMap.get(path); return props == null ? null : props.getProperties(); } public void addToPath(String path, String property) { + getProps(path).getProperties().add(property); + } + + public void addNested(String prefix, PathProperties pathProps) { + + for (Entry entry : pathProps.pathMap.entrySet()) { + + String path = pathAdd(prefix, entry.getKey()); + String[] split = SplitName.split(path); + getProps(split[0]).addProperty(split[1]); + getProps(path).addProps(entry.getValue()); + } + } + + private String pathAdd(String prefix, String key) { + return key == null ? prefix : prefix + "." + key; + } + + Props getProps(String path) { Props props = pathMap.get(path); if (props == null) { props = new Props(this, null, path); pathMap.put(path, props); } - props.getProperties().add(property); - } - - /** - * Set the properties for a given path. - */ - public void put(String path, LinkedHashSet properties) { - pathMap.put(path, new Props(this, null, path, properties)); - } - - /** - * Remove a path returning the properties set for that path. - */ - public Set remove(String path) { - Props props = pathMap.remove(path); - return props == null ? null : props.getProperties(); - } - - /** - * Return a shallow copy of the paths. - */ - public Set getPaths() { - return new LinkedHashSet(pathMap.keySet()); + return props; } public Collection getPathProps() { @@ -135,7 +103,7 @@ public class PathProperties { /** * Apply these path properties as fetch paths to the query. */ - public void apply(Query query) { + public void apply(Query query) { for (Entry entry : pathMap.entrySet()) { String path = entry.getKey(); @@ -153,6 +121,40 @@ public class PathProperties { return rootProps; } + /** + * Return true if the property (dot notation) is included in the PathProperties. + */ + public boolean includesProperty(String name) { + + String[] split = SplitName.split(name); + Props props = pathMap.get(split[0]); + return (props != null && props.includes(split[1])); + } + + /** + * Return true if the property is included using a prefix. + */ + public boolean includesProperty(String prefix, String name) { + return includesProperty(SplitName.add(prefix, name)); + } + + /** + * Return true if the fetch path is included in the PathProperties. + *

+ * The fetch path is a OneToMany or ManyToMany path in dot notation. + *

+ */ + public boolean includesPath(String path) { + return pathMap.containsKey(path); + } + + /** + * Return true if the path is included using a prefix. + */ + public boolean includesPath(String prefix, String name) { + return includesPath(SplitName.add(prefix, name)); + } + public static class Props { private final PathProperties owner; @@ -173,13 +175,6 @@ public class PathProperties { this(owner, parentPath, path, new LinkedHashSet()); } - /** - * Create a shallow copy of this Props instance. - */ - public Props copy(PathProperties newOwner) { - return new Props(newOwner, parentPath, path, new LinkedHashSet(propSet)); - } - public String getPath() { return path; } @@ -228,15 +223,15 @@ public class PathProperties { /** * Add a child Property set. */ - protected Props addChild(String subpath) { + protected Props addChild(String subPath) { - subpath = subpath.trim(); - addProperty(subpath); + subPath = subPath.trim(); + addProperty(subPath); - // build the subpath - String p = path == null ? subpath : path + "." + subpath; - Props nested = new Props(owner, path, p); - owner.pathMap.put(p, nested); + // build the subPath + String fullPath = path == null ? subPath : path + "." + subPath; + Props nested = new Props(owner, path, fullPath); + owner.pathMap.put(fullPath, nested); return nested; } @@ -246,6 +241,14 @@ public class PathProperties { protected void addProperty(String property) { propSet.add(property.trim()); } + + private void addProps(Props value) { + propSet.addAll(value.propSet); + } + + private boolean includes(String prop) { + return propSet.isEmpty() || propSet.contains(prop) || propSet.contains("*"); + } } } diff --git a/src/main/java/com/avaje/ebean/text/PathPropertiesParser.java b/src/main/java/com/avaje/ebean/text/PathPropertiesParser.java index 5381f7905..a0a1300b1 100644 --- a/src/main/java/com/avaje/ebean/text/PathPropertiesParser.java +++ b/src/main/java/com/avaje/ebean/text/PathPropertiesParser.java @@ -3,7 +3,7 @@ package com.avaje.ebean.text; /** * Parses Uri segments like :(id,name,shippingAddress(*),contacts(*)) so that * the response can be customised for performance. - * + * * @author rbygrave */ class PathPropertiesParser { @@ -50,12 +50,12 @@ class PathPropertiesParser { do { char c1 = chars[pos++]; switch (c1) { - case '(': - return currentWord(); - default: - if (pos == 1) { - return ""; - } + case '(': + return currentWord(); + default: + if (pos == 1) { + return ""; + } } } while (pos < eof); throw new RuntimeException("Hit EOF while reading sectionTitle from " + startPos); @@ -72,28 +72,30 @@ class PathPropertiesParser { } private void parseSection() { - do { - char c1 = chars[pos++]; - switch (c1) { - case '(': - addSubpath(); - break; - case ',': - addCurrentProperty(); - break; - case ':': - // start new section - startPos = pos; - return; - case ')': - // end of section - addCurrentProperty(); - popSubpath(); - break; - default: - } + if (pos < eof) { + do { + char c1 = chars[pos++]; + switch (c1) { + case '(': + addSubpath(); + break; + case ',': + addCurrentProperty(); + break; + case ':': + // start new section + startPos = pos; + return; + case ')': + // end of section + addCurrentProperty(); + popSubpath(); + break; + default: + } - } while (pos < eof); + } while (pos < eof); + } if (startPos < pos) { String currentWord = source.substring(startPos, pos); currentPathProps.addProperty(currentWord); diff --git a/src/main/java/com/avaje/ebean/text/json/JsonContext.java b/src/main/java/com/avaje/ebean/text/json/JsonContext.java index fee9e65bf..28b540016 100644 --- a/src/main/java/com/avaje/ebean/text/json/JsonContext.java +++ b/src/main/java/com/avaje/ebean/text/json/JsonContext.java @@ -1,6 +1,6 @@ package com.avaje.ebean.text.json; -import com.avaje.ebean.text.PathProperties; +import com.avaje.ebean.FetchPath; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; @@ -144,21 +144,21 @@ public interface JsonContext { void toJson(Object value, JsonGenerator generator) throws JsonIOException; /** - * Return the bean or collection as JSON string using PathProperties. + * Return the bean or collection as JSON string using FetchPath. * * @throws JsonIOException When IOException occurs */ - String toJson(Object value, PathProperties pathProperties) throws JsonIOException; + String toJson(Object value, FetchPath fetchPath) throws JsonIOException; /** - * Write the bean or collection as json to the writer using the PathProperties. + * Write the bean or collection as json to the writer using the FetchPath. */ - void toJson(Object value, Writer writer, PathProperties pathProperties) throws JsonIOException; + void toJson(Object value, Writer writer, FetchPath fetchPath) throws JsonIOException; /** - * Write the bean or collection to the JsonGenerator using the PathProperties. + * Write the bean or collection to the JsonGenerator using the FetchPath. */ - void toJson(Object value, JsonGenerator generator, PathProperties pathProperties) throws JsonIOException; + void toJson(Object value, JsonGenerator generator, FetchPath fetchPath) throws JsonIOException; /** * Deprecated in favour of using PathProperties by itself. diff --git a/src/main/java/com/avaje/ebean/text/json/JsonWriteOptions.java b/src/main/java/com/avaje/ebean/text/json/JsonWriteOptions.java index cfdfeb36d..c610d926e 100644 --- a/src/main/java/com/avaje/ebean/text/json/JsonWriteOptions.java +++ b/src/main/java/com/avaje/ebean/text/json/JsonWriteOptions.java @@ -1,5 +1,6 @@ package com.avaje.ebean.text.json; +import com.avaje.ebean.FetchPath; import com.avaje.ebean.config.JsonConfig; import com.avaje.ebean.text.PathProperties; @@ -15,7 +16,7 @@ import java.util.Map; */ public class JsonWriteOptions { - protected PathProperties pathProperties; + protected FetchPath pathProperties; protected Object objectMapper; @@ -38,7 +39,7 @@ public class JsonWriteOptions { /** * Construct JsonWriteOptions with the given pathProperties. */ - public static JsonWriteOptions pathProperties(PathProperties pathProperties) { + public static JsonWriteOptions pathProperties(FetchPath pathProperties) { JsonWriteOptions o = new JsonWriteOptions(); o.setPathProperties(pathProperties); return o; @@ -47,14 +48,14 @@ public class JsonWriteOptions { /** * Set the Map of properties to include by path. */ - public void setPathProperties(PathProperties pathProperties) { + public void setPathProperties(FetchPath pathProperties) { this.pathProperties = pathProperties; } /** * Return the properties to include by path. */ - public PathProperties getPathProperties() { + public FetchPath getPathProperties() { return pathProperties; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java index 267bd493c..fd8f841c0 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java @@ -1,15 +1,7 @@ package com.avaje.ebeaninternal.server.expression; -import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; - import com.avaje.ebean.*; import com.avaje.ebean.event.BeanQueryRequest; -import com.avaje.ebean.text.PathProperties; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; import com.avaje.ebeaninternal.api.ManyWhereJoins; import com.avaje.ebeaninternal.api.SpiExpression; @@ -18,6 +10,13 @@ import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.api.SpiExpressionValidation; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + /** * Default implementation of ExpressionList. */ @@ -173,8 +172,8 @@ public class DefaultExpressionList implements SpiExpressionList { } @Override - public Query apply(PathProperties pathProperties) { - return query.apply(pathProperties); + public Query apply(FetchPath fetchPath) { + return query.apply(fetchPath); } @Override diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java index 1f1641f2c..812540bf9 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java @@ -1,14 +1,20 @@ package com.avaje.ebeaninternal.server.expression; -import java.sql.Timestamp; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import com.avaje.ebean.*; +import com.avaje.ebean.Expression; +import com.avaje.ebean.ExpressionList; +import com.avaje.ebean.FetchPath; +import com.avaje.ebean.FutureIds; +import com.avaje.ebean.FutureList; +import com.avaje.ebean.FutureRowCount; +import com.avaje.ebean.Junction; +import com.avaje.ebean.OrderBy; +import com.avaje.ebean.PagedList; +import com.avaje.ebean.Query; +import com.avaje.ebean.QueryEachConsumer; +import com.avaje.ebean.QueryEachWhileConsumer; +import com.avaje.ebean.QueryIterator; +import com.avaje.ebean.Version; import com.avaje.ebean.event.BeanQueryRequest; -import com.avaje.ebean.text.PathProperties; import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; import com.avaje.ebeaninternal.api.ManyWhereJoins; import com.avaje.ebeaninternal.api.SpiExpression; @@ -16,6 +22,12 @@ import com.avaje.ebeaninternal.api.SpiExpressionRequest; import com.avaje.ebeaninternal.api.SpiExpressionValidation; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import java.sql.Timestamp; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + /** * Junction implementation. */ @@ -298,8 +310,8 @@ abstract class JunctionExpression implements Junction, SpiExpression, Expr } @Override - public Query apply(PathProperties pathProperties) { - return exprList.apply(pathProperties); + public Query apply(FetchPath fetchPath) { + return exprList.apply(fetchPath); } @Override diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java index ba9acce78..df609d999 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java @@ -352,8 +352,8 @@ public class CQueryBuilder { OrmQueryDetail detail = new OrmQueryDetail(); // transfer PathProperties into OrmQueryDetail - for (String path : pathProps.getPaths()) { - detail.fetch(path, pathProps.get(path)); + for (PathProperties.Props props : pathProps.getPathProps()) { + detail.fetch(props.getPath(), props.getProperties()); } // check if @Id property included in RawSql diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java index 45b6f0324..00354d496 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -10,11 +10,10 @@ import com.avaje.ebean.bean.PersistenceContext; import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebean.event.readaudit.ReadEvent; import com.avaje.ebean.plugin.BeanType; -import com.avaje.ebean.text.PathProperties; import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.api.CQueryPlanKey; import com.avaje.ebeaninternal.api.HashQuery; import com.avaje.ebeaninternal.api.ManyWhereJoins; -import com.avaje.ebeaninternal.api.CQueryPlanKey; import com.avaje.ebeaninternal.api.SpiExpression; import com.avaje.ebeaninternal.api.SpiExpressionList; import com.avaje.ebeaninternal.api.SpiExpressionValidation; @@ -26,9 +25,9 @@ import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; import com.avaje.ebeaninternal.server.deploy.DRawSqlSelect; import com.avaje.ebeaninternal.server.deploy.DeployNamedQuery; import com.avaje.ebeaninternal.server.deploy.TableJoin; +import com.avaje.ebeaninternal.server.expression.DefaultExpressionList; import com.avaje.ebeaninternal.server.expression.SimpleExpression; import com.avaje.ebeaninternal.server.query.CancelableQuery; -import com.avaje.ebeaninternal.server.expression.DefaultExpressionList; import javax.persistence.PersistenceException; import java.sql.Timestamp; @@ -284,8 +283,8 @@ public class DefaultOrmQuery implements SpiQuery { } @Override - public Query apply(PathProperties pathProperties) { - pathProperties.apply(this); + public Query apply(FetchPath fetchPath) { + fetchPath.apply(this); return this; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonContext.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonContext.java index 696419fb8..3968b3ecf 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonContext.java @@ -1,21 +1,40 @@ package com.avaje.ebeaninternal.server.text.json; +import com.avaje.ebean.FetchPath; import com.avaje.ebean.bean.EntityBean; import com.avaje.ebean.config.JsonConfig; -import com.avaje.ebean.text.json.*; -import com.avaje.ebean.text.PathProperties; +import com.avaje.ebean.text.json.EJson; +import com.avaje.ebean.text.json.JsonContext; +import com.avaje.ebean.text.json.JsonIOException; +import com.avaje.ebean.text.json.JsonReadOptions; +import com.avaje.ebean.text.json.JsonScalar; +import com.avaje.ebean.text.json.JsonWriteBeanVisitor; +import com.avaje.ebean.text.json.JsonWriteOptions; import com.avaje.ebeaninternal.api.SpiEbeanServer; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.type.TypeManager; import com.avaje.ebeaninternal.util.ParamTypeHelper; import com.avaje.ebeaninternal.util.ParamTypeHelper.ManyType; import com.avaje.ebeaninternal.util.ParamTypeHelper.TypeInfo; -import com.fasterxml.jackson.core.*; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; -import java.io.*; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; import java.lang.reflect.Type; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.Set; /** * Default implementation of JsonContext. @@ -181,9 +200,9 @@ public class DJsonContext implements JsonContext { } @Override - public void toJson(Object value, JsonGenerator generator, PathProperties pathProperties) throws JsonIOException { + public void toJson(Object value, JsonGenerator generator, FetchPath fetchPath) throws JsonIOException { // generator passed in so don't close it - toJsonNoClose(value, generator, JsonWriteOptions.pathProperties(pathProperties)); + toJsonNoClose(value, generator, JsonWriteOptions.pathProperties(fetchPath)); } @Override @@ -199,14 +218,14 @@ public class DJsonContext implements JsonContext { } @Override - public String toJson(Object value, PathProperties pathProperties) throws JsonIOException { - return toJson(value, JsonWriteOptions.pathProperties(pathProperties)); + public String toJson(Object value, FetchPath fetchPath) throws JsonIOException { + return toJson(value, JsonWriteOptions.pathProperties(fetchPath)); } @Override - public void toJson(Object o, Writer writer, PathProperties pathProperties) throws JsonIOException { + public void toJson(Object o, Writer writer, FetchPath fetchPath) throws JsonIOException { // close generator - toJsonWithClose(o, createGenerator(writer), JsonWriteOptions.pathProperties(pathProperties)); + toJsonWithClose(o, createGenerator(writer), JsonWriteOptions.pathProperties(fetchPath)); } @Override @@ -288,7 +307,7 @@ public class DJsonContext implements JsonContext { } private WriteJson createWriteJson(JsonGenerator gen, JsonWriteOptions options) { - PathProperties pathProps = (options == null) ? null : options.getPathProperties(); + FetchPath pathProps = (options == null) ? null : options.getPathProperties(); Map> visitors = (options == null) ? null : options.getVisitorMap(); return new WriteJson(server, gen, pathProps, visitors, determineObjectMapper(options), determineInclude(options)); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJson.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJson.java index 9027599f1..8fa6b2610 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJson.java +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/WriteJson.java @@ -1,8 +1,8 @@ package com.avaje.ebeaninternal.server.text.json; +import com.avaje.ebean.FetchPath; import com.avaje.ebean.bean.EntityBean; import com.avaje.ebean.config.JsonConfig; -import com.avaje.ebean.text.PathProperties; import com.avaje.ebean.text.json.JsonIOException; import com.avaje.ebean.text.json.JsonWriteBeanVisitor; import com.avaje.ebean.text.json.JsonWriter; @@ -26,7 +26,7 @@ public class WriteJson implements JsonWriter { private final JsonGenerator generator; - private final PathProperties pathProperties; + private final FetchPath fetchPath; private final Map> visitors; @@ -41,12 +41,12 @@ public class WriteJson implements JsonWriter { /** * Construct for full bean use (normal). */ - public WriteJson(SpiEbeanServer server, JsonGenerator generator, PathProperties pathProperties, + public WriteJson(SpiEbeanServer server, JsonGenerator generator, FetchPath fetchPath, Map> visitors, Object objectMapper, JsonConfig.Include include) { this.server = server; this.generator = generator; - this.pathProperties = pathProperties; + this.fetchPath = fetchPath; this.visitors = visitors; this.objectMapper = objectMapper; this.include = include; @@ -62,7 +62,7 @@ public class WriteJson implements JsonWriter { this.include = include; this.visitors = null; this.server = null; - this.pathProperties = null; + this.fetchPath = null; this.objectMapper = null; this.parentBeans = null; this.pathStack = null; @@ -376,12 +376,12 @@ public class WriteJson implements JsonWriter { String path = pathStack.peekWithNull(); JsonWriteBeanVisitor visitor = (visitors == null) ? null : visitors.get(path); - if (pathProperties == null) { + if (fetchPath == null) { return new WriteBean(desc, bean, visitor); } boolean explicitAllProps = false; - Set currentIncludeProps = pathProperties.get(path); + Set currentIncludeProps = fetchPath.getProperties(path); if (currentIncludeProps != null) { explicitAllProps = currentIncludeProps.contains("*"); if (explicitAllProps || currentIncludeProps.isEmpty()) { @@ -503,9 +503,9 @@ public class WriteJson implements JsonWriter { } public Boolean includeMany(String key) { - if (pathProperties != null) { + if (fetchPath != null) { String fullPath = pathStack.peekFullPath(key); - return pathProperties.hasPath(fullPath); + return fetchPath.hasPath(fullPath); } return null; } diff --git a/src/test/java/com/avaje/ebean/text/PathPropertiesTests.java b/src/test/java/com/avaje/ebean/text/PathPropertiesTests.java index 3e48bf2a6..f4007519a 100644 --- a/src/test/java/com/avaje/ebean/text/PathPropertiesTests.java +++ b/src/test/java/com/avaje/ebean/text/PathPropertiesTests.java @@ -1,81 +1,189 @@ package com.avaje.ebean.text; -import org.junit.Assert; +import com.avaje.ebean.FetchPath; import org.junit.Test; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + public class PathPropertiesTests { - @Test public void test_noParentheses() { - + PathProperties s0 = PathProperties.parse("id,name"); - Assert.assertEquals(1, s0.getPaths().size()); - Assert.assertTrue(s0.get(null).contains("id")); - Assert.assertTrue(s0.get(null).contains("name")); - Assert.assertFalse(s0.get(null).contains("status")); + assertEquals(1, s0.getPathProps().size()); + assertTrue(s0.getProperties(null).contains("id")); + assertTrue(s0.getProperties(null).contains("name")); + assertFalse(s0.getProperties(null).contains("status")); } @Test public void test_noParentheses_needTrim() { - + PathProperties s0 = PathProperties.parse(" id, name "); - Assert.assertEquals(1, s0.getPaths().size()); - Assert.assertTrue(s0.get(null).contains("id")); - Assert.assertTrue(s0.get(null).contains("name")); - Assert.assertFalse(s0.get(null).contains("status")); + assertEquals(1, s0.getPathProps().size()); + assertTrue(s0.getProperties(null).contains("id")); + assertTrue(s0.getProperties(null).contains("name")); + assertFalse(s0.getProperties(null).contains("status")); } - + @Test public void test_withParentheses() { PathProperties s0 = PathProperties.parse("(id,name)"); - Assert.assertEquals(1, s0.getPaths().size()); - Assert.assertTrue(s0.get(null).contains("id")); - Assert.assertTrue(s0.get(null).contains("name")); - Assert.assertFalse(s0.get(null).contains("status")); + assertEquals(1, s0.getPathProps().size()); + assertTrue(s0.getProperties(null).contains("id")); + assertTrue(s0.getProperties(null).contains("name")); + assertFalse(s0.getProperties(null).contains("status")); } - - + @Test public void test_withColon() { PathProperties s0 = PathProperties.parse(":(id,name)"); - Assert.assertEquals(1, s0.getPaths().size()); - Assert.assertTrue(s0.get(null).contains("id")); - Assert.assertTrue(s0.get(null).contains("name")); - Assert.assertFalse(s0.get(null).contains("status")); + assertEquals(1, s0.getPathProps().size()); + assertTrue(s0.getProperties(null).contains("id")); + assertTrue(s0.getProperties(null).contains("name")); + assertFalse(s0.getProperties(null).contains("status")); } - + @Test public void test_nested() { PathProperties s1 = PathProperties.parse("id,name,shipAddr(*)"); - Assert.assertEquals(2, s1.getPaths().size()); - Assert.assertEquals(3, s1.get(null).size()); - Assert.assertTrue(s1.get(null).contains("id")); - Assert.assertTrue(s1.get(null).contains("name")); - Assert.assertTrue(s1.get(null).contains("shipAddr")); - Assert.assertTrue(s1.get("shipAddr").contains("*")); - Assert.assertEquals(1, s1.get("shipAddr").size()); + assertEquals(2, s1.getPathProps().size()); + assertEquals(3, s1.getProperties(null).size()); + assertTrue(s1.getProperties(null).contains("id")); + assertTrue(s1.getProperties(null).contains("name")); + assertTrue(s1.getProperties(null).contains("shipAddr")); + assertTrue(s1.getProperties("shipAddr").contains("*")); + assertEquals(1, s1.getProperties("shipAddr").size()); } - + @Test public void test_withParenthesesColonNested() { PathProperties s1 = PathProperties.parse(":(id,name,shipAddr(*))"); - Assert.assertEquals(2, s1.getPaths().size()); - Assert.assertEquals(3, s1.get(null).size()); - Assert.assertTrue(s1.get(null).contains("id")); - Assert.assertTrue(s1.get(null).contains("name")); - Assert.assertTrue(s1.get(null).contains("shipAddr")); - Assert.assertTrue(s1.get("shipAddr").contains("*")); - Assert.assertEquals(1, s1.get("shipAddr").size()); + assertEquals(2, s1.getPathProps().size()); + assertEquals(3, s1.getProperties(null).size()); + assertTrue(s1.getProperties(null).contains("id")); + assertTrue(s1.getProperties(null).contains("name")); + assertTrue(s1.getProperties(null).contains("shipAddr")); + assertTrue(s1.getProperties("shipAddr").contains("*")); + assertEquals(1, s1.getProperties("shipAddr").size()); + } + @Test + public void test_add() { + + PathProperties root = PathProperties.parse("status,date"); + root.addNested("customer", PathProperties.parse("id,name")); + + FetchPath expect = PathProperties.parse("status,date,customer(id,name)"); + assertThat(root.toString()).isEqualTo(expect.toString()); + } + + @Test + public void test_add_nested() { + + PathProperties root = PathProperties.parse("status,date"); + root.addNested("customer", PathProperties.parse("id,name,address(line1,city)")); + + FetchPath expect = PathProperties.parse("status,date,customer(id,name,address(line1,city))"); + assertThat(root.toString()).isEqualTo(expect.toString()); + } + + @Test + public void test_all_properties() { + + FetchPath root = PathProperties.parse("*"); + assertThat(root.getProperties(null)).containsExactly("*"); + } + + @Test + public void test_all_properties_multipleLevels() { + + PathProperties root = PathProperties.parse("*,customer(*)"); + //PathProperties.Props rootProps = root.getProps(null); + PathProperties.Props customerProps = root.getProps("customer"); + + assertThat(root.getProperties(null)).containsExactly("*", "customer"); + assertThat(customerProps.getPropertiesAsString()).isEqualTo("*"); + } + + @Test + public void test_includesProperty_when_wildcardUsed() { + + PathProperties root = PathProperties.parse("*,customer(*)"); + + assertTrue(root.includesProperty("id")); + assertTrue(root.includesProperty("name")); + assertTrue(root.includesProperty("customer.id")); + assertTrue(root.includesProperty("customer.name")); + + assertFalse(root.includesProperty("details.id")); + assertTrue(root.includesProperty("details")); + + assertFalse(root.includesPath("details")); + } + + @Test + public void test_includesProperty_when_specificPropertiesUsed() { + + PathProperties root = PathProperties.parse("id,name,customer(*,billingAddress(city))"); + + assertTrue(root.includesProperty("id")); + assertTrue(root.includesProperty("name")); + assertFalse(root.includesProperty("status")); + + assertTrue(root.includesProperty("customer.id")); + assertTrue(root.includesProperty("customer.foo")); + assertTrue(root.includesProperty("customer.billingAddress")); + assertTrue(root.includesProperty("customer.billingAddress.city")); + + assertFalse(root.includesPath("customer.shippingAddress")); + assertFalse(root.includesPath("customer", "shippingAddress")); + assertFalse(root.includesProperty("customer.shippingAddress.city")); + + assertTrue(root.includesPath(null)); + assertTrue(root.includesPath("customer")); + assertTrue(root.includesPath("customer.billingAddress")); + assertTrue(root.includesPath("customer", "billingAddress")); + + assertFalse(root.includesPath("customer.shippingAddress")); + assertFalse(root.includesPath("details")); + } + + @Test + public void test_includesPropertyWithPrefix() { + + PathProperties root = PathProperties.parse("id,name,customer(*,billingAddress(city))"); + + assertTrue(root.includesProperty("customer", "id")); + assertTrue(root.includesProperty("customer", "billingAddress")); + assertTrue(root.includesProperty("customer.billingAddress", "city")); + + assertFalse(root.includesPath("customer", "shippingAddress")); + assertFalse(root.includesProperty("customer.shippingAddress", "city")); + } + + @Test + public void test_includesPathWithPrefix() { + + PathProperties root = PathProperties.parse("id,name,customer(*,billingAddress(city))"); + + assertTrue(root.includesPath(null, "customer")); + assertTrue(root.includesPath("customer", "billingAddress")); + + assertFalse(root.includesPath(null, "details")); + assertFalse(root.includesPath("customer", "shippingAddress")); } } diff --git a/src/test/java/com/avaje/ebean/text/json/JsonWriteOptionsTests.java b/src/test/java/com/avaje/ebean/text/json/JsonWriteOptionsTests.java index 287d4d7c3..c05498ae6 100644 --- a/src/test/java/com/avaje/ebean/text/json/JsonWriteOptionsTests.java +++ b/src/test/java/com/avaje/ebean/text/json/JsonWriteOptionsTests.java @@ -1,58 +1,59 @@ package com.avaje.ebean.text.json; +import com.avaje.ebean.FetchPath; import org.junit.Assert; import org.junit.Test; -import com.avaje.ebean.text.PathProperties; -import com.avaje.ebean.text.json.JsonWriteOptions; - import java.util.Set; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + public class JsonWriteOptionsTests { @Test public void test_parse() { JsonWriteOptions options = JsonWriteOptions.parsePath("id,status,name"); - PathProperties pathProps = options.getPathProperties(); + FetchPath pathProps = options.getPathProperties(); - Assert.assertEquals(1, pathProps.getPaths().size()); - Assert.assertTrue(pathProps.get(null).contains("id")); - Assert.assertTrue(pathProps.get(null).contains("name")); - Assert.assertTrue(pathProps.get(null).contains("status")); - Assert.assertFalse(pathProps.get(null).contains("foo")); + //Assert.assertEquals(1, pathProps.getPaths().size()); + assertTrue(pathProps.getProperties(null).contains("id")); + assertTrue(pathProps.getProperties(null).contains("name")); + assertTrue(pathProps.getProperties(null).contains("status")); + assertFalse(pathProps.getProperties(null).contains("foo")); } @Test public void test_with_depth() { JsonWriteOptions options = JsonWriteOptions.parsePath("id,status,name,customer(id,name,address(street,city)),orders(qty,product(sku,prodName))"); - PathProperties pathProps = options.getPathProperties(); + FetchPath pathProps = options.getPathProperties(); - Assert.assertEquals(5, pathProps.getPaths().size()); - Assert.assertTrue(pathProps.get(null).contains("id")); - Assert.assertTrue(pathProps.get(null).contains("name")); - Assert.assertTrue(pathProps.get(null).contains("status")); - Assert.assertTrue(pathProps.get(null).contains("customer")); - Assert.assertTrue(pathProps.get(null).contains("orders")); - Assert.assertFalse(pathProps.get(null).contains("foo")); + //Assert.assertEquals(5, pathProps.getPaths().size()); + assertTrue(pathProps.getProperties(null).contains("id")); + assertTrue(pathProps.getProperties(null).contains("name")); + assertTrue(pathProps.getProperties(null).contains("status")); + assertTrue(pathProps.getProperties(null).contains("customer")); + assertTrue(pathProps.getProperties(null).contains("orders")); + assertFalse(pathProps.getProperties(null).contains("foo")); - Set customer = pathProps.get("customer"); - Assert.assertTrue(customer.contains("id")); - Assert.assertTrue(customer.contains("name")); - Assert.assertTrue(customer.contains("address")); + Set customer = pathProps.getProperties("customer"); + assertTrue(customer.contains("id")); + assertTrue(customer.contains("name")); + assertTrue(customer.contains("address")); - Set address = pathProps.get("customer.address"); - Assert.assertTrue(address.contains("street")); - Assert.assertTrue(address.contains("city")); + Set address = pathProps.getProperties("customer.address"); + assertTrue(address.contains("street")); + assertTrue(address.contains("city")); - Set orders = pathProps.get("orders"); - Assert.assertTrue(orders.contains("qty")); - Assert.assertTrue(orders.contains("product")); + Set orders = pathProps.getProperties("orders"); + assertTrue(orders.contains("qty")); + assertTrue(orders.contains("product")); - Set product = pathProps.get("orders.product"); - Assert.assertTrue(product.contains("sku")); - Assert.assertTrue(product.contains("prodName")); + Set product = pathProps.getProperties("orders.product"); + assertTrue(product.contains("sku")); + assertTrue(product.contains("prodName")); } diff --git a/src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypeOffsetDateTimeTest.java b/src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypeOffsetDateTimeTest.java index 9cef51320..db565a2ea 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypeOffsetDateTimeTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypeOffsetDateTimeTest.java @@ -6,7 +6,9 @@ import org.junit.Test; import java.sql.Timestamp; import java.time.OffsetDateTime; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; public class ScalarTypeOffsetDateTimeTest {