use of com.datastax.oss.driver.internal.mapper.processor.entity.EntityDefinition in project java-driver by datastax.
the class DaoSelectMethodGenerator method generate.
@Override
public Optional<MethodSpec> generate() {
// Validate the return type:
DaoReturnType returnType = parseAndValidateReturnType(getSupportedReturnTypes(), Select.class.getSimpleName());
if (returnType == null) {
return Optional.empty();
}
TypeElement entityElement = returnType.getEntityElement();
EntityDefinition entityDefinition = context.getEntityFactory().getDefinition(entityElement);
// Validate the parameters:
// - if there is a custom clause, they are free-form (they'll be used as bind variables)
// - otherwise, we accept the primary key components or a subset thereof (possibly empty to
// select all rows), followed by free-form parameters bound to the secondary clauses (such as
// LIMIT).
// In either case, a Function<BoundStatementBuilder, BoundStatementBuilder> can be added in last
// position.
List<? extends VariableElement> parameters = methodElement.getParameters();
VariableElement boundStatementFunction = findBoundStatementFunction(methodElement);
if (boundStatementFunction != null) {
parameters = parameters.subList(0, parameters.size() - 1);
}
final List<? extends VariableElement> primaryKeyParameters;
final List<? extends VariableElement> freeFormParameters;
Select selectAnnotation = methodElement.getAnnotation(Select.class);
// otherwise we wouldn't have gotten into this class
assert selectAnnotation != null;
String customClause = selectAnnotation.customWhereClause();
if (parameters.isEmpty()) {
primaryKeyParameters = freeFormParameters = Collections.emptyList();
} else if (customClause.isEmpty()) {
// If we have a partial primary key *and* free-form parameters, things get ambiguous because
// we don't know where the primary key ends. By convention, we require the first free-form
// parameter to be annotated with @CqlName in those cases.
// So the boundary is either when we have enough parameters for a full primary key, or when we
// encounter the first annotated parameter.
int firstNamedParameter = parameters.size();
for (int i = 0; i < parameters.size(); i++) {
if (parameters.get(i).getAnnotation(CqlName.class) != null) {
firstNamedParameter = i;
break;
}
}
int primaryKeyEnd = Math.min(firstNamedParameter, entityDefinition.getPrimaryKey().size());
if (primaryKeyEnd >= parameters.size()) {
primaryKeyParameters = parameters;
freeFormParameters = Collections.emptyList();
} else {
primaryKeyParameters = parameters.subList(0, primaryKeyEnd);
freeFormParameters = parameters.subList(primaryKeyEnd, parameters.size());
}
} else {
primaryKeyParameters = Collections.emptyList();
freeFormParameters = parameters;
}
// If we have parameters for some primary key components, validate that the types match:
if (!primaryKeyParameters.isEmpty() && !EntityUtils.areParametersValid(entityElement, entityDefinition, primaryKeyParameters, Select.class, context, methodElement, processedType, "don't use a custom clause")) {
return Optional.empty();
}
// Generate the method:
String helperFieldName = enclosingClass.addEntityHelperField(ClassName.get(entityElement));
String statementName = enclosingClass.addPreparedStatement(methodElement, (methodBuilder, requestName) -> generateSelectRequest(methodBuilder, requestName, helperFieldName, primaryKeyParameters.size()));
CodeBlock.Builder createStatementBlock = CodeBlock.builder();
createStatementBlock.addStatement("$T boundStatementBuilder = $L.boundStatementBuilder()", BoundStatementBuilder.class, statementName);
populateBuilderWithStatementAttributes(createStatementBlock, methodElement);
populateBuilderWithFunction(createStatementBlock, boundStatementFunction);
if (!primaryKeyParameters.isEmpty()) {
List<CodeBlock> primaryKeyNames = entityDefinition.getPrimaryKey().stream().map(PropertyDefinition::getCqlName).collect(Collectors.toList()).subList(0, primaryKeyParameters.size());
GeneratedCodePatterns.bindParameters(primaryKeyParameters, primaryKeyNames, createStatementBlock, enclosingClass, context, false);
}
if (!freeFormParameters.isEmpty()) {
if (validateCqlNamesPresent(freeFormParameters)) {
GeneratedCodePatterns.bindParameters(freeFormParameters, createStatementBlock, enclosingClass, context, false);
} else {
return Optional.empty();
}
}
createStatementBlock.addStatement("$T boundStatement = boundStatementBuilder.build()", BoundStatement.class);
return crudMethod(createStatementBlock, returnType, helperFieldName);
}
use of com.datastax.oss.driver.internal.mapper.processor.entity.EntityDefinition in project java-driver by datastax.
the class DaoUpdateMethodGenerator method generate.
@Override
public Optional<MethodSpec> generate() {
// Validate the parameters:
// - the first one must be the entity.
// - the others are completely free-form (they'll be used as additional bind variables)
// A Function<BoundStatementBuilder, BoundStatementBuilder> can be added in last position.
List<? extends VariableElement> parameters = methodElement.getParameters();
VariableElement boundStatementFunction = findBoundStatementFunction(methodElement);
if (boundStatementFunction != null) {
parameters = parameters.subList(0, parameters.size() - 1);
}
TypeElement entityElement = parameters.isEmpty() ? null : EntityUtils.asEntityElement(parameters.get(0), typeParameters);
if (entityElement == null) {
context.getMessager().error(methodElement, "%s methods must take the entity to update as the first parameter", Update.class.getSimpleName());
return Optional.empty();
}
warnIfCqlNamePresent(parameters.subList(0, 1));
EntityDefinition entityDefinition = context.getEntityFactory().getDefinition(entityElement);
// Validate the return type:
DaoReturnType returnType = parseAndValidateReturnType(getSupportedReturnTypes(), Update.class.getSimpleName());
if (returnType == null) {
return Optional.empty();
}
// Generate the method:
String helperFieldName = enclosingClass.addEntityHelperField(ClassName.get(entityElement));
String statementName = enclosingClass.addPreparedStatement(methodElement, (methodBuilder, requestName) -> generatePrepareRequest(methodBuilder, requestName, helperFieldName));
CodeBlock.Builder createStatementBlock = CodeBlock.builder();
createStatementBlock.addStatement("$T boundStatementBuilder = $L.boundStatementBuilder()", BoundStatementBuilder.class, statementName);
populateBuilderWithStatementAttributes(createStatementBlock, methodElement);
populateBuilderWithFunction(createStatementBlock, boundStatementFunction);
String entityParameterName = parameters.get(0).getSimpleName().toString();
Update annotation = methodElement.getAnnotation(Update.class);
String customWhereClause = annotation.customWhereClause();
NullSavingStrategy nullSavingStrategy = nullSavingStrategyValidation.getNullSavingStrategy(Update.class, Update::nullSavingStrategy, methodElement, enclosingClass);
if (customWhereClause.isEmpty()) {
// We generated an update by primary key (see maybeAddWhereClause), all entity properties are
// present as placeholders.
createStatementBlock.addStatement("$1L.set($2L, boundStatementBuilder, $3T.$4L, false)", helperFieldName, entityParameterName, NullSavingStrategy.class, nullSavingStrategy);
} else {
createStatementBlock.addStatement("$1T nullSavingStrategy = $1T.$2L", NullSavingStrategy.class, nullSavingStrategy);
// (if the custom clause has custom placeholders, this will be addressed below)
for (PropertyDefinition property : entityDefinition.getRegularColumns()) {
GeneratedCodePatterns.setValue(property.getCqlName(), property.getType(), CodeBlock.of("$L.$L()", entityParameterName, property.getGetterName()), "boundStatementBuilder", createStatementBlock, enclosingClass, true, false);
}
}
// customIfClause
if (parameters.size() > 1) {
List<? extends VariableElement> bindMarkers = parameters.subList(1, parameters.size());
if (validateCqlNamesPresent(bindMarkers)) {
GeneratedCodePatterns.bindParameters(bindMarkers, createStatementBlock, enclosingClass, context, false);
} else {
return Optional.empty();
}
}
createStatementBlock.addStatement("$T boundStatement = boundStatementBuilder.build()", BoundStatement.class);
return crudMethod(createStatementBlock, returnType, helperFieldName);
}
use of com.datastax.oss.driver.internal.mapper.processor.entity.EntityDefinition in project java-driver by datastax.
the class DaoDeleteMethodGenerator method generate.
@Override
public Optional<MethodSpec> generate() {
Delete annotation = methodElement.getAnnotation(Delete.class);
assert annotation != null;
if (annotation.ifExists() && !annotation.customIfClause().isEmpty()) {
context.getMessager().error(methodElement, "Invalid annotation parameters: %s cannot have both ifExists and customIfClause", Delete.class.getSimpleName());
return Optional.empty();
}
// Validate the arguments: either an entity instance, or the PK components (in the latter case,
// the entity class has to be provided via the annotation).
// In either case, a Function<BoundStatementBuilder, BoundStatementBuilder> can be added in last
// position.
List<? extends VariableElement> parameters = methodElement.getParameters();
VariableElement boundStatementFunction = findBoundStatementFunction(methodElement);
if (boundStatementFunction != null) {
parameters = parameters.subList(0, parameters.size() - 1);
}
TypeElement entityElement;
EntityDefinition entityDefinition;
boolean hasEntityParameter;
if (parameters.isEmpty()) {
context.getMessager().error(methodElement, "Wrong number of parameters: %s methods with no custom clause " + "must take either an entity instance, or the primary key components", Delete.class.getSimpleName());
return Optional.empty();
}
String customWhereClause = annotation.customWhereClause();
String customIfClause = annotation.customIfClause();
VariableElement firstParameter = parameters.get(0);
entityElement = EntityUtils.asEntityElement(firstParameter, typeParameters);
hasEntityParameter = (entityElement != null);
// the number of primary key parameters provided, if -1 this implies a custom
// where clause where number of parameters that are primary key are irrelevant.
final int primaryKeyParameterCount;
if (hasEntityParameter) {
if (!customWhereClause.isEmpty()) {
context.getMessager().error(methodElement, "Invalid parameter list: %s methods that have a custom where clause " + "must not take an Entity (%s) as a parameter", Delete.class.getSimpleName(), entityElement.getSimpleName());
}
entityDefinition = context.getEntityFactory().getDefinition(entityElement);
primaryKeyParameterCount = entityDefinition.getPrimaryKey().size();
} else {
entityElement = getEntityClassFromAnnotation(Delete.class);
if (entityElement == null) {
context.getMessager().error(methodElement, "Missing entity class: %s methods that do not operate on an entity " + "instance must have an 'entityClass' argument", Delete.class.getSimpleName());
return Optional.empty();
} else {
entityDefinition = context.getEntityFactory().getDefinition(entityElement);
}
if (customWhereClause.isEmpty()) {
/* if a custom if clause is provided, the whole primary key must also be provided.
* we only do this check if there is no custom where clause as the order of
* keys may differ in that case.*/
List<? extends VariableElement> primaryKeyParameters = parameters;
if (!customIfClause.isEmpty()) {
if (primaryKeyParameters.size() < entityDefinition.getPrimaryKey().size()) {
List<TypeName> primaryKeyTypes = entityDefinition.getPrimaryKey().stream().map(d -> d.getType().asTypeName()).collect(Collectors.toList());
context.getMessager().error(methodElement, "Invalid parameter list: %s methods that have a custom if clause" + "must specify the entire primary key (expected primary keys of %s: %s)", Delete.class.getSimpleName(), entityElement.getSimpleName(), primaryKeyTypes);
return Optional.empty();
} else {
// restrict parameters to primary key length.
primaryKeyParameters = primaryKeyParameters.subList(0, entityDefinition.getPrimaryKey().size());
}
}
primaryKeyParameterCount = primaryKeyParameters.size();
if (!EntityUtils.areParametersValid(entityElement, entityDefinition, primaryKeyParameters, Delete.class, context, methodElement, processedType, "do not operate on an entity instance and lack a custom where clause")) {
return Optional.empty();
}
} else {
primaryKeyParameterCount = -1;
}
}
// Validate the return type:
DaoReturnType returnType = parseAndValidateReturnType(getSupportedReturnTypes(), Delete.class.getSimpleName());
if (returnType == null) {
return Optional.empty();
}
// Generate the method
String helperFieldName = enclosingClass.addEntityHelperField(ClassName.get(entityElement));
String statementName = enclosingClass.addPreparedStatement(methodElement, (methodBuilder, requestName) -> generatePrepareRequest(methodBuilder, requestName, helperFieldName, primaryKeyParameterCount));
CodeBlock.Builder createStatementBlock = CodeBlock.builder();
createStatementBlock.addStatement("$T boundStatementBuilder = $L.boundStatementBuilder()", BoundStatementBuilder.class, statementName);
populateBuilderWithStatementAttributes(createStatementBlock, methodElement);
populateBuilderWithFunction(createStatementBlock, boundStatementFunction);
int nextParameterIndex = 0;
if (hasEntityParameter) {
warnIfCqlNamePresent(Collections.singletonList(firstParameter));
// Bind entity's PK properties
for (PropertyDefinition property : entityDefinition.getPrimaryKey()) {
GeneratedCodePatterns.setValue(property.getCqlName(), property.getType(), CodeBlock.of("$L.$L()", firstParameter.getSimpleName(), property.getGetterName()), "boundStatementBuilder", createStatementBlock, enclosingClass);
}
nextParameterIndex = 1;
} else if (customWhereClause.isEmpty()) {
// The PK components are passed as arguments to the method (we've already checked that the
// types match).
List<CodeBlock> primaryKeyNames = entityDefinition.getPrimaryKey().stream().map(PropertyDefinition::getCqlName).collect(Collectors.toList()).subList(0, primaryKeyParameterCount);
List<? extends VariableElement> bindMarkers = parameters.subList(0, primaryKeyParameterCount);
warnIfCqlNamePresent(bindMarkers);
GeneratedCodePatterns.bindParameters(bindMarkers, primaryKeyNames, createStatementBlock, enclosingClass, context, false);
nextParameterIndex = primaryKeyNames.size();
}
// Bind any remaining parameters, assuming they are values for a custom WHERE or IF clause
if (nextParameterIndex < parameters.size()) {
if (customIfClause.isEmpty() && customWhereClause.isEmpty()) {
context.getMessager().error(methodElement, "Wrong number of parameters: %s methods can only have additional " + "parameters if they specify a custom WHERE or IF clause", Delete.class.getSimpleName());
}
List<? extends VariableElement> bindMarkers = parameters.subList(nextParameterIndex, parameters.size());
if (validateCqlNamesPresent(bindMarkers)) {
GeneratedCodePatterns.bindParameters(bindMarkers, createStatementBlock, enclosingClass, context, false);
} else {
return Optional.empty();
}
}
createStatementBlock.addStatement("$T boundStatement = boundStatementBuilder.build()", BoundStatement.class);
return crudMethod(createStatementBlock, returnType, helperFieldName);
}
use of com.datastax.oss.driver.internal.mapper.processor.entity.EntityDefinition in project java-driver by datastax.
the class EntityUtils method areParametersValid.
/**
* Validates that the given parameters are valid for an {@link EntityDefinition}, meaning that
* there are at least enough parameters provided to match the number of partition key columns and
* that parameter types match the primary key types.
*
* <p>If it is determined that the parameters are not valid, false is returned and an error
* message is emitted on the given method element.
*/
public static boolean areParametersValid(TypeElement entityElement, EntityDefinition entityDefinition, List<? extends VariableElement> parameters, Class<? extends Annotation> annotationClass, ProcessorContext context, ExecutableElement methodElement, TypeElement processedType, String exceptionCondition) {
if (exceptionCondition == null || exceptionCondition.isEmpty()) {
exceptionCondition = "";
} else {
exceptionCondition = " that " + exceptionCondition;
}
List<TypeName> primaryKeyTypes = entityDefinition.getPrimaryKey().stream().map(d -> d.getType().asTypeName()).collect(Collectors.toList());
List<TypeName> partitionKeyTypes = entityDefinition.getPartitionKey().stream().map(d -> d.getType().asTypeName()).collect(Collectors.toList());
List<TypeName> parameterTypes = parameters.stream().map(p -> TypeName.get(p.asType())).collect(Collectors.toList());
// if parameters are provided, we must have at least enough to match partition key.
if (parameterTypes.size() < partitionKeyTypes.size()) {
context.getMessager().error(methodElement, "Invalid parameter list: %s methods%s " + "must at least specify partition key components " + "(expected partition key of %s: %s)", annotationClass.getSimpleName(), exceptionCondition, entityElement.getSimpleName(), partitionKeyTypes);
return false;
}
if (parameterTypes.size() > primaryKeyTypes.size()) {
context.getMessager().error(methodElement, "Invalid parameter list: %s methods%s " + "must match the primary key components in the exact order " + "(expected primary key of %s: %s). Too many parameters provided", annotationClass.getSimpleName(), exceptionCondition, entityElement.getSimpleName(), primaryKeyTypes);
return false;
}
// validate that each parameter type matches the primary key type
for (int parameterIndex = 0; parameterIndex < parameterTypes.size(); parameterIndex++) {
TypeName parameterType = parameterTypes.get(parameterIndex);
TypeName primaryKeyParameterType = primaryKeyTypes.get(parameterIndex);
if (!parameterType.equals(primaryKeyParameterType)) {
context.getMessager().error(methodElement, "Invalid parameter list: %s methods%s " + "must match the primary key components in the exact order " + "(expected primary key of %s: %s). Mismatch at index %d: %s should be %s", annotationClass.getSimpleName(), exceptionCondition, entityElement.getSimpleName(), primaryKeyTypes, parameterIndex, parameterType, primaryKeyParameterType);
return false;
}
}
return true;
}
use of com.datastax.oss.driver.internal.mapper.processor.entity.EntityDefinition in project java-driver by datastax.
the class DaoIncrementMethodGenerator method generate.
@Override
public Optional<MethodSpec> generate() {
TypeElement entityElement = getEntityClassFromAnnotation(Increment.class);
EntityDefinition entityDefinition;
if (entityElement == null) {
context.getMessager().error(methodElement, "Missing entity class: %s methods must always have an 'entityClass' argument", Increment.class.getSimpleName());
return Optional.empty();
} else {
entityDefinition = context.getEntityFactory().getDefinition(entityElement);
}
// Validate the parameters:
// - all the PK components of the entity, in order.
// - one or more increment parameters that must match non-PK columns.
// - a Function<BoundStatementBuilder, BoundStatementBuilder> can be added in last position.
List<? extends VariableElement> parameters = methodElement.getParameters();
VariableElement boundStatementFunction = findBoundStatementFunction(methodElement);
if (boundStatementFunction != null) {
parameters = parameters.subList(0, parameters.size() - 1);
}
List<? extends VariableElement> primaryKeyParameters = parameters;
// Must have at least enough parameters for the full PK
if (primaryKeyParameters.size() < entityDefinition.getPrimaryKey().size()) {
List<TypeName> primaryKeyTypes = entityDefinition.getPrimaryKey().stream().map(d -> d.getType().asTypeName()).collect(Collectors.toList());
context.getMessager().error(methodElement, "Invalid parameter list: %s methods must specify the entire primary key " + "(expected primary keys of %s: %s)", Increment.class.getSimpleName(), entityElement.getSimpleName(), primaryKeyTypes);
return Optional.empty();
} else {
primaryKeyParameters = primaryKeyParameters.subList(0, entityDefinition.getPrimaryKey().size());
warnIfCqlNamePresent(primaryKeyParameters);
}
// PK parameter types must match
if (!EntityUtils.areParametersValid(entityElement, entityDefinition, primaryKeyParameters, Increment.class, context, methodElement, processedType, "")) {
return Optional.empty();
}
// The remaining parameters are the increments to the counter columns
List<? extends VariableElement> incrementParameters = parameters.subList(primaryKeyParameters.size(), parameters.size());
if (!validateCqlNamesPresent(incrementParameters)) {
return Optional.empty();
}
for (VariableElement parameter : incrementParameters) {
TypeMirror type = parameter.asType();
if (type.getKind() != TypeKind.LONG && !context.getClassUtils().isSame(type, Long.class)) {
context.getMessager().error(methodElement, "Invalid argument type: increment parameters of %s methods can only be " + "primitive longs or java.lang.Long. Offending parameter: '%s' (%s)", Increment.class.getSimpleName(), parameter.getSimpleName(), type);
return Optional.empty();
}
}
// Validate the return type:
DaoReturnType returnType = parseAndValidateReturnType(getSupportedReturnTypes(), Increment.class.getSimpleName());
if (returnType == null) {
return Optional.empty();
}
// Generate the method:
String helperFieldName = enclosingClass.addEntityHelperField(ClassName.get(entityElement));
String statementName = enclosingClass.addPreparedStatement(methodElement, (methodBuilder, requestName) -> generatePrepareRequest(methodBuilder, requestName, entityDefinition, helperFieldName, incrementParameters));
CodeBlock.Builder updateStatementBlock = CodeBlock.builder();
updateStatementBlock.addStatement("$T boundStatementBuilder = $L.boundStatementBuilder()", BoundStatementBuilder.class, statementName);
populateBuilderWithStatementAttributes(updateStatementBlock, methodElement);
populateBuilderWithFunction(updateStatementBlock, boundStatementFunction);
// Bind the counter increments. The bind parameter names are always the raw parameter names, see
// generatePrepareRequest.
List<CodeBlock> bindMarkerNames = incrementParameters.stream().map(p -> CodeBlock.of("$S", p.getSimpleName())).collect(Collectors.toList());
// Force the null saving strategy. This will fail if the user targets Cassandra 2.2, but
// SET_TO_NULL would not work with counters anyway.
updateStatementBlock.addStatement("final $1T nullSavingStrategy = $1T.$2L", NullSavingStrategy.class, NullSavingStrategy.DO_NOT_SET);
GeneratedCodePatterns.bindParameters(incrementParameters, bindMarkerNames, updateStatementBlock, enclosingClass, context, true);
// Bind the PK columns
List<CodeBlock> primaryKeyNames = entityDefinition.getPrimaryKey().stream().map(PropertyDefinition::getCqlName).collect(Collectors.toList());
GeneratedCodePatterns.bindParameters(primaryKeyParameters, primaryKeyNames, updateStatementBlock, enclosingClass, context, false);
updateStatementBlock.addStatement("$T boundStatement = boundStatementBuilder.build()", BoundStatement.class);
return crudMethod(updateStatementBlock, returnType, helperFieldName);
}
Aggregations