Skip to content

Commit

Permalink
Fixed #357 - Added support for a 'this' expression in entity views
Browse files Browse the repository at this point in the history
  • Loading branch information
beikov committed Mar 27, 2017
1 parent 70fec2c commit 41d91ac
Show file tree
Hide file tree
Showing 63 changed files with 2,537 additions and 586 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Not yet released
* Support fetches for entity mappings in entity views
* Automatic rewrites of id expressions in equality predicates to avoid joins
* Various performance improvements
* Support referring to `this` in all mapping types for putting values in embedded objects

### Bug fixes

Expand Down
4 changes: 1 addition & 3 deletions core/parser/src/main/antlr4/imports/JPQL_lexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ Date_literal : '{' 'd' (' ' | '\t')+ '\'' Date_string '\'' (' ' | '\t')* '}';

Time_literal : '{' 't' (' ' | '\t')+ '\'' Time_string '\'' (' ' | '\t')* '}';

Timestamp_literal : '{' 'ts' (' ' | '\t')+ '\'' Date_string ' ' Time_string Nanos_string '\'' (' ' | '\t')* '}';
Timestamp_literal : '{' 'ts' (' ' | '\t')+ '\'' Date_string ' ' Time_string ('.' DIGIT*)? '\'' (' ' | '\t')* '}';

Boolean_literal
: [Tt][Rr][Uu][Ee]
Expand Down Expand Up @@ -211,8 +211,6 @@ fragment Date_string : DIGIT DIGIT DIGIT DIGIT '-' DIGIT DIGIT '-' DIGIT DIGIT;

fragment Time_string : DIGIT DIGIT? ':' DIGIT DIGIT ':' DIGIT DIGIT;

fragment Nanos_string : ('.' DIGIT*)?;

fragment DIGIT: '0'..'9';
fragment DIGIT_NOT_ZERO: '1'..'9';
fragment ZERO: '0';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,14 @@ public class PrefixingAndAliasReplacementQueryGenerator extends SimpleQueryGener
private final String substitute;
private final String alias;
private final String aliasToSkip;
private final boolean skipPrefix;

public PrefixingAndAliasReplacementQueryGenerator(String prefix, String substitute, String alias) {
this(prefix, substitute, alias, null);
}

public PrefixingAndAliasReplacementQueryGenerator(String prefix, String substitute, String alias, String aliasToSkip) {
public PrefixingAndAliasReplacementQueryGenerator(String prefix, String substitute, String alias, String aliasToSkip, boolean skipPrefix) {
this.prefix = prefix;
this.substitute = substitute;
this.alias = alias;
this.aliasToSkip = aliasToSkip;
this.skipPrefix = skipPrefix;
}

@Override
Expand All @@ -53,10 +51,15 @@ public void visit(PathExpression expression) {
if (size == 1) {
PathElementExpression elementExpression = expressions.get(0);
if (elementExpression instanceof PropertyExpression) {
if (alias.equals(((PropertyExpression) elementExpression).getProperty())) {
String property = ((PropertyExpression) elementExpression).getProperty();
if (alias.equals(property)) {
sb.append(substitute);
return;
}
if (skipPrefix && prefix.equals(property)) {
super.visit(expression);
return;
}
}
} else if (aliasToSkip != null) {
PathElementExpression elementExpression = expressions.get(0);
Expand All @@ -68,6 +71,7 @@ public void visit(PathExpression expression) {
}
}
sb.append(prefix);
sb.append('.');
super.visit(expression);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@
import java.util.List;

/**
* This is a visitor that can be used to collect expression modifier references into an expression.
* When a visit method returns {@linkplain Boolean#TRUE}, an expression modifier for the expression is generated
* and the {@link #onModifier(ExpressionModifier)} method is called. The modifier is bound to the embedding expression
* i.e. the parent expression and can be used for reading or replacing the expression.
*
* @author Moritz Becker
* @author Christian Beikov
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
import java.util.List;

/**
* This is a visitor that can be used to do inplace changes to an expression.
* This is quite similar to {@link ExpressionModifierCollectingResultVisitorAdapter},
* but more targeted for multiple possibly nested replacements.
*
* @author Moritz Becker
* @author Christian Beikov
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,56 @@ There is also the possibility to specify a <<anchor-fetch-strategies,_fetch stra
{projectname} entity views generally supports the full set of link:{core_doc}#expressions[expressions] that JPQL and {projectname} core module supports,
but in addition to that, also offers some expression extensions.

==== THIS

Similar to the `this` expression in Java, in a mapping expression within entity views the `this` expression can be used to refer to the entity type backing the entity view.
The expression can be used to implement embedded objects that are able to refer to the entity type of the entity view.

[source,java]
----
@EntityView(Cat.class)
interface EmbeddedCatView {
@IdMapping("id")
Long getId();
String getName();
}
@EmbeddableEntityView(Cat.class)
interface ExternalInterfaceView {
@Mapping("name")
String getExternalName();
}
@EntityView(Cat.class)
interface CatView {
@IdMapping("id")
Long getId();
@Mapping("this")
EmbeddedCatView getEmbedded();
@Mapping("this")
ExternalInterfaceView getAdapter();
}
----

Both `EmbeddedCatView` and `ExternalInterfaceView` refer to the same `Cat` as their parent `CatView`.
The query looks as if the types were directly embedded into the entity view.

[source,sql]
----
SELECT
cat.id,
cat.id,
cat.name,
cat.name
FROM Cat cat
----

==== OUTER

In {projectname} core the `OUTER` function can be used to refer to the query root of a parent query from within a subquery.
Expand Down Expand Up @@ -1009,9 +1059,6 @@ TIP: Make sure you understand the <<anchor-select-fetch-strategy-view-root,impli
// ==== QUERY_ROOT
// TODO: add a macro for referencing the query root

// ==== THIS
// TODO: add a macro for referencing "this"

[[anchor-constructor-mapping]]
=== Entity View constructor mapping

Expand Down
6 changes: 6 additions & 0 deletions entity-view/impl/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@
<artifactId>javaee-api</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.blazebit.persistence.impl.expression.MacroFunction;
import com.blazebit.persistence.view.CorrelationBuilder;
import com.blazebit.persistence.view.CorrelationProvider;
import com.blazebit.persistence.view.impl.metamodel.AbstractAttribute;
import com.blazebit.persistence.view.spi.ViewRootJpqlMacro;

/**
Expand Down Expand Up @@ -53,18 +54,19 @@ private CorrelationProviderProxyBase(Class<?> correlated, String correlationKeyA
@Override
public void applyCorrelation(CorrelationBuilder correlationBuilder, String correlationExpression) {
String alias = correlationBuilder.getCorrelationAlias();
String prefix = alias + ".";

// Find out the view root alias
ExpressionFactory expressionFactory = correlationBuilder.getService(ExpressionFactory.class);
MacroFunction viewRootFunction = expressionFactory.getDefaultMacroConfiguration().get("VIEW_ROOT");
ViewRootJpqlMacro viewRootMacro = (ViewRootJpqlMacro) viewRootFunction.getState()[0];

// Prefix all paths except view root alias based ones and substitute the key alias with the correlation expression
PrefixingAndAliasReplacementQueryGenerator generator = new PrefixingAndAliasReplacementQueryGenerator(prefix, correlationExpression, correlationKeyAlias, viewRootMacro.getViewRoot());
String viewRoot = viewRootMacro.getViewRoot();
PrefixingAndAliasReplacementQueryGenerator generator = new PrefixingAndAliasReplacementQueryGenerator(alias, correlationExpression, correlationKeyAlias, viewRoot, true);
StringBuilder buffer = new StringBuilder(approximateExpressionSize);
generator.setQueryBuffer(buffer);
Expression expression = expressionFactory.createBooleanExpression(this.correlationExpression, false);
String expressionString = AbstractAttribute.replaceThisFromMapping(this.correlationExpression, alias);
Expression expression = expressionFactory.createBooleanExpression(expressionString, false);
expression.accept(generator);

correlationBuilder.correlate(correlated)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ public EntityViewManagerImpl(EntityViewConfigurationImpl config, CriteriaBuilder
Set<Class<?>> entityViews = config.getEntityViews();

Set<String> errors = new HashSet<String>();
MetamodelBuildingContext context = new MetamodelBuildingContextImpl(cbf.getService(EntityMetamodel.class), cbf.getService(ExpressionFactory.class), proxyFactory, entityViews, errors);
ExpressionFactory expressionFactory = cbf.getService(ExpressionFactory.class);
MetamodelBuildingContext context = new MetamodelBuildingContextImpl(cbf.getService(EntityMetamodel.class), expressionFactory, proxyFactory, entityViews, errors);
this.metamodel = new ViewMetamodelImpl(entityViews, cbf, context, validateExpressions);

if (!errors.isEmpty()) {
Expand Down Expand Up @@ -125,11 +126,11 @@ public EntityViewManagerImpl(EntityViewConfigurationImpl config, CriteriaBuilder
for (ViewType<?> view : metamodel.getViews()) {
// TODO: Might be a good idea to let the view root be overridden or specified via the annotation
String probableViewRoot = StringUtils.firstToLower(view.getEntityClass().getSimpleName());
ExpressionFactory expressionFactory = context.createMacroAwareExpressionFactory(probableViewRoot);
getTemplate(expressionFactory, view, null, null);
ExpressionFactory macroAwareExpressionFactory = context.createMacroAwareExpressionFactory(probableViewRoot);
getTemplate(macroAwareExpressionFactory, view, null, null);

for (MappingConstructor<?> constructor : view.getConstructors()) {
getTemplate(expressionFactory, view, (MappingConstructor) constructor, null);
getTemplate(macroAwareExpressionFactory, view, (MappingConstructor) constructor, null);
}
}
} else if (Boolean.valueOf(String.valueOf(properties.get(ConfigurationProperties.PROXY_EAGER_LOADING)))) {
Expand Down Expand Up @@ -269,23 +270,29 @@ private <T> Constructor<T> findConstructor(Constructor<T>[] constructors, Class<
}

@SuppressWarnings("unchecked")
public void applyObjectBuilder(Class<?> clazz, String mappingConstructorName, String entityViewRoot, EntityViewConfiguration configuration) {
public String applyObjectBuilder(Class<?> clazz, String mappingConstructorName, String entityViewRoot, EntityViewConfiguration configuration) {
ViewType<?> viewType = getMetamodel().view(clazz);
if (viewType == null) {
throw new IllegalArgumentException("There is no entity view for the class '" + clazz.getName() + "' registered!");
}
MappingConstructor<?> mappingConstructor = viewType.getConstructor(mappingConstructorName);
applyObjectBuilder(viewType, mappingConstructor, viewType.getName(), entityViewRoot, configuration.getCriteriaBuilder(), configuration, 0, true);
return applyObjectBuilder(viewType, mappingConstructor, viewType.getName(), entityViewRoot, configuration.getCriteriaBuilder(), configuration, 0, true);
}

public void applyObjectBuilder(ViewType<?> viewType, MappingConstructor<?> mappingConstructor, String viewName, String entityViewRoot, FullQueryBuilder<?, ?> criteriaBuilder, EntityViewConfiguration configuration, int offset, boolean registerMacro) {
criteriaBuilder.selectNew(createObjectBuilder(viewType, mappingConstructor, viewName, entityViewRoot, criteriaBuilder, configuration, offset, registerMacro));
public String applyObjectBuilder(ViewType<?> viewType, MappingConstructor<?> mappingConstructor, String viewName, String entityViewRoot, FullQueryBuilder<?, ?> criteriaBuilder, EntityViewConfiguration configuration, int offset, boolean registerMacro) {
From root = getFromByViewRoot(criteriaBuilder, entityViewRoot);
criteriaBuilder.selectNew(createObjectBuilder(viewType, mappingConstructor, viewName, root, criteriaBuilder, configuration, offset, registerMacro));
return root.getAlias();
}

public ObjectBuilder<?> createObjectBuilder(ViewType<?> viewType, MappingConstructor<?> mappingConstructor, String viewName, String entityViewRoot, FullQueryBuilder<?, ?> criteriaBuilder, EntityViewConfiguration configuration, int offset, boolean registerMacro) {
From root = getFromByViewRoot(criteriaBuilder, entityViewRoot);
return createObjectBuilder(viewType, mappingConstructor, viewName, root, criteriaBuilder, configuration, offset, registerMacro);
}

public ObjectBuilder<?> createObjectBuilder(ViewType<?> viewType, MappingConstructor<?> mappingConstructor, String viewName, From root, FullQueryBuilder<?, ?> criteriaBuilder, EntityViewConfiguration configuration, int offset, boolean registerMacro) {
Class<?> entityClazz = root.getType();
entityViewRoot = root.getAlias();
String entityViewRoot = root.getAlias();
ExpressionFactory ef = criteriaBuilder.getService(ExpressionFactory.class);
if (!viewType.getEntityClass().isAssignableFrom(entityClazz)) {
throw new IllegalArgumentException("The given view type with the entity type '" + viewType.getEntityClass().getName()
Expand Down
Loading

0 comments on commit 41d91ac

Please sign in to comment.