Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.evitadb.api.proxy.impl.entityBuilder.SetReferenceMethodClassifier Maven / Gradle / Ivy
/*
*
* _ _ ____ ____
* _____ _(_) |_ __ _| _ \| __ )
* / _ \ \ / / | __/ _` | | | | _ \
* | __/\ V /| | || (_| | |_| | |_) |
* \___| \_/ |_|\__\__,_|____/|____/
*
* Copyright (c) 2023
*
* Licensed under the Business Source License, Version 1.1 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/FgForrest/evitaDB/blob/master/LICENSE
*
* 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 io.evitadb.api.proxy.impl.entityBuilder;
import io.evitadb.api.exception.ContextMissingException;
import io.evitadb.api.exception.EntityClassInvalidException;
import io.evitadb.api.exception.ReferenceNotFoundException;
import io.evitadb.api.proxy.SealedEntityProxy;
import io.evitadb.api.proxy.SealedEntityProxy.ProxyType;
import io.evitadb.api.proxy.impl.SealedEntityProxyState;
import io.evitadb.api.requestResponse.data.EntityClassifier;
import io.evitadb.api.requestResponse.data.EntityContract;
import io.evitadb.api.requestResponse.data.EntityEditor.EntityBuilder;
import io.evitadb.api.requestResponse.data.ReferenceContract;
import io.evitadb.api.requestResponse.data.SealedEntity;
import io.evitadb.api.requestResponse.data.annotation.CreateWhenMissing;
import io.evitadb.api.requestResponse.data.annotation.Entity;
import io.evitadb.api.requestResponse.data.annotation.EntityRef;
import io.evitadb.api.requestResponse.data.annotation.RemoveWhenExists;
import io.evitadb.api.requestResponse.schema.Cardinality;
import io.evitadb.api.requestResponse.schema.EntitySchemaContract;
import io.evitadb.api.requestResponse.schema.ReferenceSchemaContract;
import io.evitadb.dataType.EvitaDataTypes;
import io.evitadb.exception.EvitaInvalidUsageException;
import io.evitadb.utils.Assert;
import io.evitadb.utils.NumberUtils;
import io.evitadb.utils.ReflectionLookup;
import one.edee.oss.proxycian.CurriedMethodContextInvocationHandler;
import one.edee.oss.proxycian.DirectMethodClassification;
import one.edee.oss.proxycian.utils.GenericsUtils;
import one.edee.oss.proxycian.utils.GenericsUtils.GenericBundle;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.function.Consumer;
import java.util.stream.Stream;
import static io.evitadb.api.proxy.impl.entity.GetReferenceMethodClassifier.getReferenceSchema;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static java.util.Optional.ofNullable;
/**
* Identifies methods that are used to set entity references into an entity and provides their implementation.
*
* @author Jan Novotný ([email protected] ), FG Forrest a.s. (c) 2023
*/
public class SetReferenceMethodClassifier extends DirectMethodClassification {
/**
* We may reuse singleton instance since advice is stateless.
*/
public static final SetReferenceMethodClassifier INSTANCE = new SetReferenceMethodClassifier();
/**
* Asserts that the entity annotation on the referenced class is consistent with the entity type of the parent.
*
* @param reflectionLookup the reflection lookup
* @param referenceSchema reference schema that is being processed
* @param parameterType the parameter type to look for the annotation on
* @param consumerType the return type to look for the annotation on
*/
@Nonnull
public static Optional recognizeCallContext(
@Nonnull ReflectionLookup reflectionLookup,
@Nonnull ReferenceSchemaContract referenceSchema,
@Nullable Class> returnType,
@Nullable ResolvedParameter parameterType,
@Nullable Class> consumerType
) {
final Optional parameterEntityInstance = parameterType == null ? empty() : ofNullable(reflectionLookup.getClassAnnotation(parameterType.resolvedType(), Entity.class));
final Optional parameterEntityRefInstance = parameterType == null ? empty() : ofNullable(reflectionLookup.getClassAnnotation(parameterType.resolvedType(), EntityRef.class));
final Optional consumerTypeEntityInstance = consumerType == null ? empty() : ofNullable(reflectionLookup.getClassAnnotation(consumerType, Entity.class));
final Optional consumerTypeEntityRefInstance = consumerType == null ? empty() : ofNullable(reflectionLookup.getClassAnnotation(consumerType, EntityRef.class));
final Optional returnTypeEntityInstance = returnType == null ? empty() : ofNullable(reflectionLookup.getClassAnnotation(returnType, Entity.class));
final Optional returnTypeEntityRefInstance = returnType == null ? empty() : ofNullable(reflectionLookup.getClassAnnotation(returnType, EntityRef.class));
final Optional referencedEntityType = Stream.of(
parameterEntityInstance.map(Entity::name),
parameterEntityRefInstance.map(EntityRef::value),
consumerTypeEntityInstance.map(Entity::name),
consumerTypeEntityRefInstance.map(EntityRef::value),
returnTypeEntityInstance.map(Entity::name),
returnTypeEntityRefInstance.map(EntityRef::value)
)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
final EntityRecognizedIn recognizedIn;
final ResolvedParameter entityContract;
if (parameterEntityInstance.isPresent() || parameterEntityRefInstance.isPresent()) {
entityContract = parameterType;
recognizedIn = EntityRecognizedIn.PARAMETER;
} else if (consumerTypeEntityInstance.isPresent() || consumerTypeEntityRefInstance.isPresent()) {
entityContract = new ResolvedParameter(consumerType, consumerType);
recognizedIn = EntityRecognizedIn.CONSUMER;
} else if (returnTypeEntityInstance.isPresent() || returnTypeEntityRefInstance.isPresent()) {
entityContract = new ResolvedParameter(returnType, returnType);
recognizedIn = EntityRecognizedIn.RETURN_TYPE;
} else {
return empty();
}
final String expectedReferencedEntityType = referenceSchema.getReferencedEntityType();
if (referencedEntityType.map(it -> it.equals(expectedReferencedEntityType)).orElse(false)) {
return of(
new RecognizedContext(
recognizedIn, entityContract, expectedReferencedEntityType, false
)
);
} else if (referencedEntityType.map(it -> it.equals(referenceSchema.getReferencedGroupType())).orElse(false)) {
return of(
new RecognizedContext(
recognizedIn, entityContract, expectedReferencedEntityType, true
)
);
} else if (referenceSchema.getReferencedGroupType() == null) {
//noinspection rawtypes
throw new EntityClassInvalidException(
entityContract.resolvedType(),
"Referenced class type `" + entityContract.resolvedType() + "` must represent " +
"entity type `" + expectedReferencedEntityType + "`, " +
"but " +
(consumerType != null || parameterType != null ? "neither the parameter type `" + ofNullable((Class) consumerType).orElse(parameterType.resolvedType()).getName() + "` nor " : "") +
"the return type `" + returnType.getName() + "` is annotated with @Entity referencing `" +
referencedEntityType.orElse("N/A") + "` entity type!"
);
} else {
//noinspection rawtypes
throw new EntityClassInvalidException(
entityContract.resolvedType(),
"Referenced class type `" + entityContract.resolvedType() + "` must represent " +
"either entity type `" + expectedReferencedEntityType + "` or " +
"`" + referenceSchema.getReferencedGroupType() + "` (group), " +
(consumerType != null || parameterType != null ? "neither the parameter type `" + ofNullable((Class) consumerType).orElse(parameterType.resolvedType()).getName() + "` nor " : "") +
"the return type `" + returnType.getName() + "` is annotated with @Entity referencing `" +
referencedEntityType.orElse("N/A") + "` entity type!"
);
}
}
/**
* Returns true if the entity annotation is recognized in the given scope.
*
* @param entityRecognizedIn the entity recognized in
* @param scope the scope to check
* @return true if the entity annotation is recognized in the given scope
*/
public static boolean isEntityRecognizedIn(
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Nonnull Optional entityRecognizedIn,
@Nonnull EntityRecognizedIn scope
) {
return entityRecognizedIn.map(RecognizedContext::recognizedIn).map(scope::equals).orElse(false);
}
/**
* Returns true if referenced type is assignable from the given class.
*
* @param referencedType the referenced type
* @param aClass the class to check
* @return true if referenced type is assignable from the given class
*/
@Nonnull
public static Boolean resolvedTypeIs(
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Nonnull Optional referencedType,
@Nonnull Class> aClass
) {
return referencedType.map(ResolvedParameter::resolvedType).map(aClass::isAssignableFrom).orElse(false);
}
/**
* Returns true if referenced type is assignable from the given class.
*
* @param referencedType the referenced type
* @return true if referenced type is assignable from the given class
*/
@Nonnull
public static Boolean resolvedTypeIsNumber(
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Nonnull Optional referencedType
) {
return referencedType.map(ResolvedParameter::resolvedType).map(NumberUtils::isIntConvertibleNumber).orElse(false);
}
/**
* Method returns the referenced entity type and verifies that it is managed by evitaDB.
*
* @param referenceSchema the reference schema
* @return the referenced entity type
*/
@Nullable
private static String getReferencedType(@Nonnull ReferenceSchemaContract referenceSchema, boolean requireManagedOnly) {
final String referencedEntityType = referenceSchema.getReferencedEntityType();
Assert.isTrue(
!requireManagedOnly || referenceSchema.isReferencedEntityTypeManaged(),
"Referenced entity group type `" + referencedEntityType + "` is not managed " +
"by evitaDB and cannot be created by method call!"
);
return referencedEntityType;
}
/**
* Return a method implementation that creates new proxy object representing a reference to and external entity
* and returns the reference to the reference proxy to allow chaining (builder pattern).
*
* @param referenceSchema the reference schema to use
* @param expectedType the expected type of the referenced entity proxy
* @param referenceIdLocation the location of the reference id in the method arguments
* @param consumerLocation the location of the consumer in the method arguments
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler getOrCreateReferenceWithIdAndEntityBuilderResult(
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull Class> expectedType,
int referenceIdLocation,
int consumerLocation
) {
return (proxy, theMethod, args, theState, invokeSuper) -> {
getOrCreateReferenceWithId(referenceSchema, expectedType, referenceIdLocation, consumerLocation, args, theState);
return proxy;
};
}
/**
* Return a method implementation that creates new proxy object representing a reference to and external entity
* and returns no result.
*
* @param referenceSchema the reference schema to use
* @param expectedType the expected type of the referenced entity proxy
* @param referenceIdLocation the location of the reference id in the method arguments
* @param consumerLocation the location of the consumer in the method arguments
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler getOrCreateReferenceWithIdAndVoidResult(
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull Class> expectedType,
int referenceIdLocation,
int consumerLocation
) {
return (proxy, theMethod, args, theState, invokeSuper) -> {
getOrCreateReferenceWithId(referenceSchema, expectedType, referenceIdLocation, consumerLocation, args, theState);
return proxy;
};
}
/**
* Create or retrieve a reference instance to an external entity with a specified ID.
*
* @param referenceSchema The reference schema to use.
* @param expectedType The expected type of the referenced entity proxy.
* @param referenceIdLocation The index of the reference ID in the method arguments.
* @param consumerLocation The index of the consumer in the method arguments.
* @param args The method arguments.
* @param theState The proxy state.
*/
private static void getOrCreateReferenceWithId(
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull Class> expectedType,
int referenceIdLocation,
int consumerLocation,
@Nonnull Object[] args,
@Nonnull SealedEntityProxyState theState
) {
final String referenceName = referenceSchema.getName();
final int referencedId = EvitaDataTypes.toTargetType((Serializable) args[referenceIdLocation], int.class);
final Optional reference = theState.entityBuilder()
.getReference(referenceName, referencedId);
//noinspection unchecked
final Object referenceProxy = reference
.map(referenceContract -> theState.getOrCreateEntityReferenceProxy((Class) expectedType, referenceContract))
.orElseGet(
() -> {
theState.entityBuilder().setReference(referenceSchema.getName(), referencedId);
return theState.createEntityReferenceProxy(
theState.getEntitySchema(), referenceSchema, (Class) expectedType, ProxyType.REFERENCE,
referencedId
);
}
);
//noinspection unchecked
final Consumer consumer = (Consumer) args[consumerLocation];
consumer.accept(referenceProxy);
}
/**
* Return a method implementation that creates new proxy object representing a reference to and external entity
* and returns the reference to the reference proxy to allow chaining (builder pattern).
*
* @param referenceSchema the reference schema to use
* @param expectedType the expected type of the referenced entity proxy
* @param referenceIdLocation the location of the reference id in the method arguments
* @param consumerLocation the location of the consumer in the method arguments
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler getAndUpdateReferenceWithIdAndEntityBuilderResult(
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull Class> expectedType,
int referenceIdLocation,
int consumerLocation
) {
return (proxy, theMethod, args, theState, invokeSuper) -> {
getAndUpdateReferenceWithId(referenceSchema, expectedType, referenceIdLocation, consumerLocation, args, theState);
return proxy;
};
}
/**
* Return a method implementation that creates new proxy object representing a reference to and external entity
* and returns no result.
*
* @param referenceSchema the reference schema to use
* @param expectedType the expected type of the referenced entity proxy
* @param referenceIdLocation the location of the reference id in the method arguments
* @param consumerLocation the location of the consumer in the method arguments
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler getAndUpdateReferenceWithIdAndVoidResult(
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull Class> expectedType,
int referenceIdLocation,
int consumerLocation
) {
return (proxy, theMethod, args, theState, invokeSuper) -> {
getAndUpdateReferenceWithId(referenceSchema, expectedType, referenceIdLocation, consumerLocation, args, theState);
return null;
};
}
/**
* Performs a get and update operation on a reference with the given ID.
*
* @param referenceSchema the reference schema to use
* @param expectedType the expected type of the referenced entity proxy
* @param referenceIdLocation the location of the reference ID in the method arguments
* @param consumerLocation the location of the consumer in the method arguments
* @param args the method arguments
* @param theState the sealed entity proxy state
*/
private static void getAndUpdateReferenceWithId(
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull Class> expectedType,
int referenceIdLocation,
int consumerLocation,
@Nonnull Object[] args,
@Nonnull SealedEntityProxyState theState
) {
final String referenceName = referenceSchema.getName();
final int referencedId = EvitaDataTypes.toTargetType((Serializable) args[referenceIdLocation], int.class);
final Optional reference = theState.entityBuilder()
.getReference(referenceName, referencedId);
final Object referenceProxy;
if (reference.isEmpty()) {
throw new ReferenceNotFoundException(referenceName, referencedId, theState.entity());
} else {
//noinspection unchecked
referenceProxy = theState.getOrCreateEntityReferenceProxy((Class) expectedType, reference.get());
}
//noinspection unchecked
final Consumer consumer = (Consumer) args[consumerLocation];
consumer.accept(referenceProxy);
}
/**
* Return a method implementation that creates new proxy object representing a referenced external entity
* and returns the reference to the entity proxy to allow chaining (builder pattern).
*
* @param referenceSchema the reference schema to use
* @param expectedType the expected type of the referenced entity proxy
* @param referenceIdLocation the location of the reference id in the method arguments
* @param consumerLocation the location of the consumer in the method arguments
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler createReferencedEntityWithIdAndEntityBuilderResult(
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull Class> expectedType,
int referenceIdLocation,
int consumerLocation
) {
return (proxy, theMethod, args, theState, invokeSuper) -> {
createReferencedEntityWithId(referenceSchema, expectedType, referenceIdLocation, consumerLocation, args, theState);
return proxy;
};
}
/**
* Return a method implementation that creates new proxy object representing a referenced external entity
* and returns no result.
*
* @param referenceSchema the reference schema to use
* @param expectedType the expected type of the referenced entity proxy
* @param referenceIdLocation the location of the reference id in the method arguments
* @param consumerLocation the location of the consumer in the method arguments
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler createReferencedEntityWithIdAndVoidResult(
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull Class> expectedType,
int referenceIdLocation,
int consumerLocation
) {
return (proxy, theMethod, args, theState, invokeSuper) -> {
createReferencedEntityWithId(referenceSchema, expectedType, referenceIdLocation, consumerLocation, args, theState);
return null;
};
}
/**
* Create a new proxy object representing a referenced external entity, using the provided reference schema,
* expected type, reference ID location, consumer location, method arguments, and proxy state.
*
* @param referenceSchema The reference schema to use
* @param expectedType The expected type of the referenced entity proxy
* @param referenceIdLocation The location of the reference ID in the method arguments
* @param consumerLocation The location of the consumer in the method arguments
* @param args The method arguments
* @param theState The proxy state
*/
private static void createReferencedEntityWithId(
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull Class> expectedType,
int referenceIdLocation,
int consumerLocation,
@Nonnull Object[] args,
@Nonnull SealedEntityProxyState theState
) {
final EntityBuilder entityBuilder = theState.entityBuilder();
final int referencedId = EvitaDataTypes.toTargetType((Serializable) args[referenceIdLocation], int.class);
entityBuilder.setReference(referenceSchema.getName(), referencedId);
final Object referencedEntityInstance = theState.createEntityReferenceProxy(
theState.getEntitySchema(),
referenceSchema,
expectedType,
ProxyType.REFERENCE,
referencedId
);
//noinspection unchecked
final Consumer consumer = (Consumer) args[consumerLocation];
consumer.accept(referencedEntityInstance);
}
/**
* Return a method implementation that creates new proxy object representing a reference to and external entity
* (without knowing its primary key since it hasn't been assigned yet) and returns the reference to the entity proxy
* to allow chaining (builder pattern).
*
* @param referenceSchema the reference schema to use
* @param expectedType the expected type of the referenced entity proxy
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler createReferencedEntityWithEntityBuilderResult(
@Nonnull EntitySchemaContract referencedEntitySchema,
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull Class> expectedType
) {
return (proxy, theMethod, args, theState, invokeSuper) -> {
createReferencedEntity(referencedEntitySchema, referenceSchema, expectedType, args, theState);
return proxy;
};
}
/**
* Return a method implementation that creates new proxy object representing a reference to and external entity
* (without knowing its primary key since it hasn't been assigned yet) and returns no result.
*
* @param referenceSchema the reference schema to use
* @param expectedType the expected type of the referenced entity proxy
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler createReferencedEntityWithVoidResult(
@Nonnull EntitySchemaContract referencedEntitySchema,
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull Class> expectedType
) {
return (proxy, theMethod, args, theState, invokeSuper) -> {
createReferencedEntity(referencedEntitySchema, referenceSchema, expectedType, args, theState);
return null;
};
}
/**
* Create a new proxy object representing a reference to an external entity
* without knowing its primary key since it hasn't been assigned yet.
*
* @param referencedEntitySchema the schema of the referenced entity
* @param referenceSchema the reference schema to use
* @param expectedType the expected type of the referenced entity proxy
* @param args the arguments passed to the method
* @param theState the state of the proxy
*/
private static void createReferencedEntity(
@Nonnull EntitySchemaContract referencedEntitySchema,
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull Class> expectedType,
@Nonnull Object[] args,
@Nonnull SealedEntityProxyState theState
) {
final EntityBuilder entityBuilder = theState.entityBuilder();
final Object referencedEntityInstance = theState.createReferencedEntityProxyWithCallback(
referencedEntitySchema,
expectedType,
ProxyType.REFERENCED_ENTITY,
entityReference -> entityBuilder.setReference(referenceSchema.getName(), entityReference.getPrimaryKey())
);
//noinspection unchecked
final Consumer consumer = (Consumer) args[0];
consumer.accept(referencedEntityInstance);
}
/**
* Return a method implementation that creates new proxy object representing a reference to and external entity
* (without knowing its primary key since it hasn't been assigned yet) and returns the reference to the entity proxy
* to allow chaining (builder pattern).
*
* @param referenceSchema the reference schema to use
* @param expectedType the expected type of the referenced entity proxy
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler updateReferencedEntityWithEntityBuilderResult(
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull Class> expectedType
) {
return (proxy, theMethod, args, theState, invokeSuper) -> {
updateReferencedEntity(referenceSchema, expectedType, args, theState);
return proxy;
};
}
/**
* Return a method implementation that creates new proxy object representing a reference to and external entity
* (without knowing its primary key since it hasn't been assigned yet) and returns no result.
*
* @param referenceSchema the reference schema to use
* @param expectedType the expected type of the referenced entity proxy
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler updateReferencedEntityWithVoidResult(
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull Class> expectedType
) {
return (proxy, theMethod, args, theState, invokeSuper) -> {
updateReferencedEntity(referenceSchema, expectedType, args, theState);
return null;
};
}
/**
* Updates the referenced entity using consumer logic.
*
* @param referenceSchema the reference schema to use
* @param expectedType the expected type of the referenced entity proxy
* @param args the arguments to pass to the updateReferencedEntity method
* @param theState the sealed entity proxy state
*/
private static void updateReferencedEntity(
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull Class> expectedType,
@Nonnull Object[] args,
@Nonnull SealedEntityProxyState theState
) {
final EntityBuilder entityBuilder = theState.entityBuilder();
final Collection references = entityBuilder.getReferences(referenceSchema.getName());
if (references.isEmpty()) {
throw ContextMissingException.referenceContextMissing(referenceSchema.getName());
} else {
final Object referencedEntityInstance = theState.getOrCreateReferencedEntityProxy(
expectedType,
references.iterator().next().getReferencedEntity()
.orElseThrow(() -> ContextMissingException.referencedEntityContextMissing(entityBuilder.getType(), referenceSchema.getName())),
ProxyType.REFERENCED_ENTITY
);
//noinspection unchecked
final Consumer consumer = (Consumer) args[0];
consumer.accept(referencedEntityInstance);
}
}
/**
* Return a method implementation that creates new proxy object representing a reference to and external entity
* and returns the reference to the created proxy allowing to set reference properties on it.
*
* @param referenceSchema the reference schema to use
* @param expectedType the expected type of the referenced entity proxy
* @return the method implementation
*/
@SuppressWarnings({"rawtypes", "unchecked"})
@Nonnull
private static CurriedMethodContextInvocationHandler getOrCreateByIdWithReferenceResult(
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull Class expectedType
) {
final String referenceName = referenceSchema.getName();
return (entityClassifier, theMethod, args, theState, invokeSuper) -> {
final int referencedId = EvitaDataTypes.toTargetType((Serializable) args[0], int.class);
final Collection references = theState.entityBuilder()
.getReferences(referenceName);
if (references.isEmpty()) {
theState.entityBuilder().setReference(referenceSchema.getName(), referencedId);
return theState.createEntityReferenceProxy(
theState.getEntitySchema(), referenceSchema, expectedType, ProxyType.REFERENCE,
referencedId
);
} else {
final ReferenceContract firstReference = references.iterator().next();
return theState.getOrCreateEntityReferenceProxy(expectedType, firstReference);
}
};
}
/**
* Return a method implementation that creates new proxy object representing a reference to and external entity
* (without knowing its primary key since it hasn't been assigned yet) and returns the reference to the created
* proxy allowing to set reference properties on it.
*
* @param referenceSchema the reference schema to use
* @param expectedType the expected type of the referenced entity proxy
* @return the method implementation
*/
@SuppressWarnings({"rawtypes", "unchecked"})
@Nonnull
private static CurriedMethodContextInvocationHandler getOrCreateWithReferencedEntityResult(
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull Class expectedType
) {
final String referencedEntityType = getReferencedType(referenceSchema, true);
final String referenceName = referenceSchema.getName();
return (entityClassifier, theMethod, args, theState, invokeSuper) -> {
final EntityBuilder entityBuilder = theState.entityBuilder();
final Collection references = entityBuilder
.getReferences(referenceName);
if (references.isEmpty()) {
return theState.createReferencedEntityProxyWithCallback(
theState.getEntitySchemaOrThrow(referencedEntityType), expectedType, ProxyType.REFERENCED_ENTITY,
entityReference -> entityBuilder.setReference(referenceName, entityReference.primaryKey())
);
} else {
final ReferenceContract firstReference = references.iterator().next();
final Optional> referencedInstance = theState.getReferencedEntityObjectIfPresent(
referencedEntityType, firstReference.getReferencedPrimaryKey(),
expectedType, ProxyType.REFERENCED_ENTITY
);
if (referencedInstance.isPresent()) {
return referencedInstance.get();
} else {
Assert.isTrue(
firstReference.getReferencedEntity().isPresent(),
() -> ContextMissingException.referencedEntityContextMissing(theState.getType(), referenceName)
);
return firstReference.getReferencedEntity()
.map(it -> theState.getOrCreateReferencedEntityProxy(expectedType, it, ProxyType.REFERENCED_ENTITY))
.orElse(null);
}
}
};
}
/**
* Return a method implementation that removes the single reference if exists.
*
* @param referenceSchema the reference schema to use
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler removeReference(
@Nonnull SealedEntityProxyState proxyState,
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull ResolvedParameter resolvedParameter,
boolean entityRecognizedInReturnType
) {
final Class> returnType = resolvedParameter.resolvedType();
if (Collection.class.equals(resolvedParameter.mainType()) || List.class.equals(resolvedParameter.mainType())) {
if (Number.class.isAssignableFrom(returnType)) {
return (proxy, theMethod, args, theState, invokeSuper) -> {
final EntityBuilder entityBuilder = theState.entityBuilder();
final Collection references = entityBuilder.getReferences(referenceSchema.getName());
references.forEach(it -> entityBuilder.removeReference(referenceSchema.getName(), it.getReferencedPrimaryKey()));
return references.stream()
.map(it -> it.getReferenceKey().primaryKey())
.toList();
};
} else {
return removeReferencesAndReturnTheirProxies(referenceSchema, returnType, entityRecognizedInReturnType);
}
} else if (returnType.equals(void.class)) {
return (proxy, theMethod, args, theState, invokeSuper) -> {
final EntityBuilder entityBuilder = theState.entityBuilder();
final Collection references = entityBuilder.getReferences(referenceSchema.getName());
references.forEach(it -> entityBuilder.removeReference(referenceSchema.getName(), it.getReferencedPrimaryKey()));
return null;
};
} else if (Boolean.class.isAssignableFrom(returnType)) {
return (proxy, theMethod, args, theState, invokeSuper) -> {
final EntityBuilder entityBuilder = theState.entityBuilder();
final Collection references = entityBuilder.getReferences(referenceSchema.getName());
references.forEach(it -> entityBuilder.removeReference(referenceSchema.getName(), it.getReferencedPrimaryKey()));
return !references.isEmpty();
};
} else {
if (returnType.equals(proxyState.getProxyClass())) {
return (proxy, theMethod, args, theState, invokeSuper) -> {
final EntityBuilder entityBuilder = theState.entityBuilder();
final Collection references = entityBuilder.getReferences(referenceSchema.getName());
if (references.isEmpty()) {
// do nothing
} else if (references.size() == 1) {
entityBuilder.removeReference(
referenceSchema.getName(),
references.iterator().next().getReferencedPrimaryKey()
);
} else {
throw new EvitaInvalidUsageException(
"Cannot remove reference `" + referenceSchema.getName() +
"` from entity `" + theState.getEntitySchema().getName() + "` " +
"because there is more than single reference!"
);
}
return proxy;
};
} else if (Number.class.isAssignableFrom(returnType)) {
return (proxy, theMethod, args, theState, invokeSuper) -> {
final EntityBuilder entityBuilder = theState.entityBuilder();
final Collection references = entityBuilder.getReferences(referenceSchema.getName());
if (references.isEmpty()) {
// do nothing
return null;
} else if (references.size() == 1) {
final int referencedPrimaryKey = references.iterator().next().getReferencedPrimaryKey();
entityBuilder.removeReference(
referenceSchema.getName(),
referencedPrimaryKey
);
//noinspection unchecked,rawtypes
return EvitaDataTypes.toTargetType(referencedPrimaryKey, (Class) returnType);
} else {
throw new EvitaInvalidUsageException(
"Cannot remove reference `" + referenceSchema.getName() +
"` from entity `" + theState.getEntitySchema().getName() + "` " +
"because there is more than single reference!"
);
}
};
} else {
return removeReferenceAndReturnItsProxy(referenceSchema, returnType, entityRecognizedInReturnType);
}
}
}
/**
* Method implementation that removes the single reference if exists and returns the removed reference proxy.
*
* @param referenceSchema the reference schema to use
* @param returnType the return type
* @param entityRecognizedInReturnType true if the entity annotation is recognized in the return type
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler removeReferenceAndReturnItsProxy(
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull Class> returnType,
boolean entityRecognizedInReturnType
) {
return (proxy, theMethod, args, theState, invokeSuper) -> {
final EntityBuilder entityBuilder = theState.entityBuilder();
final String referenceName = referenceSchema.getName();
final Collection references = entityBuilder.getReferences(referenceName);
if (references.isEmpty()) {
// do nothing
return null;
} else if (references.size() == 1) {
final int referencedPrimaryKey = references.iterator().next().getReferencedPrimaryKey();
final Optional reference = entityBuilder.getReference(referenceName, referencedPrimaryKey);
if (reference.isPresent()) {
entityBuilder.removeReference(referenceName, referencedPrimaryKey);
if (entityRecognizedInReturnType) {
if (reference.get().getReferencedEntity().isPresent()) {
final SealedEntity referencedEntity = reference.get().getReferencedEntity().get();
return theState.getOrCreateReferencedEntityProxy(
returnType, referencedEntity, ProxyType.REFERENCED_ENTITY
);
} else {
throw ContextMissingException.referencedEntityContextMissing(theState.getType(), referenceName);
}
} else {
return theState.getOrCreateEntityReferenceProxy(returnType, reference.get());
}
} else {
return null;
}
} else {
throw new EvitaInvalidUsageException(
"Cannot remove reference `" + referenceSchema.getName() +
"` from entity `" + theState.getEntitySchema().getName() + "` " +
"because there is more than single reference!"
);
}
};
}
/**
* Method implementation that removes the multiple references if they exist and returns all the removed reference
* proxies.
*
* @param referenceSchema the reference schema to use
* @param returnType the return type
* @param entityRecognizedInReturnType true if the entity annotation is recognized in the return type
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler removeReferencesAndReturnTheirProxies(
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull Class> returnType,
boolean entityRecognizedInReturnType
) {
return (proxy, theMethod, args, theState, invokeSuper) -> {
final EntityBuilder entityBuilder = theState.entityBuilder();
final String referenceName = referenceSchema.getName();
final Collection references = entityBuilder.getReferences(referenceName);
if (references.isEmpty()) {
// do nothing
return Collections.emptyList();
} else {
final List removedReferences = new ArrayList<>(references.size());
for (ReferenceContract referenceToRemove : references) {
if (entityRecognizedInReturnType) {
if (referenceToRemove.getReferencedEntity().isPresent()) {
final SealedEntity referencedEntity = referenceToRemove.getReferencedEntity().get();
removedReferences.add(
theState.getOrCreateReferencedEntityProxy(
returnType, referencedEntity, ProxyType.REFERENCED_ENTITY
)
);
} else {
throw ContextMissingException.referencedEntityContextMissing(theState.getType(), referenceName);
}
} else {
removedReferences.add(
theState.getOrCreateEntityReferenceProxy(returnType, referenceToRemove)
);
}
entityBuilder.removeReference(referenceName, referenceToRemove.getReferencedPrimaryKey());
}
return removedReferences;
}
};
}
/**
* Returns method implementation that sets the referenced entity by extracting it from {@link EntityClassifier}
* and return no result.
*
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferencedEntityClassifierWithVoidResult(
@Nonnull ReferenceSchemaContract referenceSchema
) {
final String referenceName = referenceSchema.getName();
final String expectedEntityType = getReferencedType(referenceSchema, false);
final Cardinality cardinality = referenceSchema.getCardinality();
return (proxy, theMethod, args, theState, invokeSuper) -> {
setReferencedEntityClassifier(referenceName, expectedEntityType, cardinality, args, theState);
return null;
};
}
/**
* Returns method implementation that sets the referenced entity by extracting it from {@link EntityClassifier}
* and return the reference to the proxy to allow chaining (builder pattern).
*
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferencedEntityClassifierWithBuilderResult(
@Nonnull ReferenceSchemaContract referenceSchema
) {
final String referenceName = referenceSchema.getName();
final String expectedEntityType = getReferencedType(referenceSchema, false);
final Cardinality cardinality = referenceSchema.getCardinality();
return (proxy, theMethod, args, theState, invokeSuper) -> {
setReferencedEntityClassifier(referenceName, expectedEntityType, cardinality, args, theState);
return proxy;
};
}
/**
* Sets the referenced entity classifier by extracting it from the EntityClassifier object.
*
* @param referenceName the name of the reference
* @param expectedEntityType the expected type of the referenced entity
* @param cardinality the cardinality of the reference
* @param args the arguments passed to the method
* @param theState the state of the sealed entity proxy
*/
private static void setReferencedEntityClassifier(
@Nonnull String referenceName,
@Nonnull String expectedEntityType,
@Nonnull Cardinality cardinality,
@Nonnull Object[] args,
@Nonnull SealedEntityProxyState theState
) {
final EntityBuilder entityBuilder = theState.entityBuilder();
final EntityClassifier referencedClassifier = (EntityClassifier) args[0];
if (referencedClassifier == null && (cardinality == Cardinality.ZERO_OR_ONE || cardinality == Cardinality.EXACTLY_ONE)) {
final Collection references = entityBuilder.getReferences(referenceName);
Assert.isPremiseValid(references.size() < 2, "Cardinality is `" + cardinality + "` but there are more than one reference!");
references.forEach(it -> entityBuilder.removeReference(referenceName, it.getReferencedPrimaryKey()));
} else {
Assert.isTrue(
expectedEntityType.equals(referencedClassifier.getType()),
"Entity type `" + referencedClassifier.getType() + "` in passed argument " +
"doesn't match the referenced entity type: `" + expectedEntityType + "`!"
);
if (cardinality == Cardinality.ZERO_OR_ONE || cardinality == Cardinality.EXACTLY_ONE) {
final Collection references = entityBuilder.getReferences(referenceName);
Assert.isPremiseValid(references.size() < 2, "Cardinality is `" + cardinality + "` but there are more than one reference!");
if (!references.isEmpty()) {
final ReferenceContract singleReference = references.iterator().next();
if (singleReference.getReferencedPrimaryKey() == referencedClassifier.getPrimaryKey()) {
// do nothing the reference is already set
return;
} else {
// remove existing reference and registered object
entityBuilder.removeReference(referenceName, singleReference.getReferencedPrimaryKey());
}
}
}
entityBuilder.setReference(referenceName, referencedClassifier.getPrimaryKey());
}
}
/**
* Returns method implementation that sets the referenced entity by extracting it from {@link EntityClassifier}
* and return no result.
*
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferencedEntityClassifierAsArrayWithBuilderResult(
@Nonnull ReferenceSchemaContract referenceSchema
) {
final String referenceName = referenceSchema.getName();
final String expectedEntityType = getReferencedType(referenceSchema, false);
return (proxy, theMethod, args, theState, invokeSuper) -> {
setReferencedEntityClassifierAsArray(referenceName, expectedEntityType, args, theState);
return proxy;
};
}
/**
* Returns method implementation that sets the referenced entity by extracting it from {@link EntityClassifier}
* and return the reference to the proxy to allow chaining (builder pattern).
*
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferencedEntityClassifierAsArrayWithVoidResult(
@Nonnull ReferenceSchemaContract referenceSchema
) {
final String referenceName = referenceSchema.getName();
final String expectedEntityType = getReferencedType(referenceSchema, false);
return (proxy, theMethod, args, theState, invokeSuper) -> {
setReferencedEntityClassifierAsArray(referenceName, expectedEntityType, args, theState);
return proxy;
};
}
/**
* Sets the referenced entity classifier as an array of {@link EntityClassifier}.
*
* @param referenceName the name of the reference
* @param expectedEntityType the expected entity type
* @param args the array of referenced entity classifiers
* @param theState the sealed entity proxy state
*/
private static void setReferencedEntityClassifierAsArray(
@Nonnull String referenceName,
@Nonnull String expectedEntityType,
@Nonnull Object[] args,
@Nonnull SealedEntityProxyState theState
) {
final EntityBuilder entityBuilder = theState.entityBuilder();
final Object[] referencedClassifierArray = (Object[]) args[0];
for (Object referencedClassifierObject : referencedClassifierArray) {
final EntityClassifier referencedClassifier = (EntityClassifier) referencedClassifierObject;
Assert.isTrue(
expectedEntityType.equals(referencedClassifier.getType()),
"Entity type `" + referencedClassifier.getType() + "` in passed argument " +
"doesn't match the referenced entity type: `" + expectedEntityType + "`!"
);
entityBuilder.setReference(referenceName, referencedClassifier.getPrimaryKey());
}
}
/**
* Returns method implementation that sets the referenced entity by extracting it from {@link EntityClassifier}
* and return no result.
*
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferencedEntityClassifierAsCollectionWithBuilderResult(
@Nonnull ReferenceSchemaContract referenceSchema
) {
final String referenceName = referenceSchema.getName();
final String expectedEntityType = getReferencedType(referenceSchema, false);
return (proxy, theMethod, args, theState, invokeSuper) -> {
setReferencedEntityClassifierAsCollection(referenceName, expectedEntityType, args, theState);
return proxy;
};
}
/**
* Returns method implementation that sets the referenced entity by extracting it from {@link EntityClassifier}
* and return the reference to the proxy to allow chaining (builder pattern).
*
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferencedEntityClassifierAsCollectionWithVoidResult(
@Nonnull ReferenceSchemaContract referenceSchema
) {
final String referenceName = referenceSchema.getName();
final String expectedEntityType = getReferencedType(referenceSchema, false);
return (proxy, theMethod, args, theState, invokeSuper) -> {
setReferencedEntityClassifierAsCollection(referenceName, expectedEntityType, args, theState);
return proxy;
};
}
/**
* Sets the referenced entity classifier as a collection by extracting it from {@link EntityClassifier}.
*
* @param referenceName the name of the reference
* @param expectedEntityType the expected entity type of the referenced classifier
* @param args the arguments passed to the method
* @param theState the state of the sealed entity proxy
*/
private static void setReferencedEntityClassifierAsCollection(
@Nonnull String referenceName,
@Nonnull String expectedEntityType,
@Nonnull Object[] args,
@Nonnull SealedEntityProxyState theState
) {
final EntityBuilder entityBuilder = theState.entityBuilder();
// noinspection unchecked
final Collection referencedClassifierArray = (Collection) args[0];
for (EntityClassifier referencedClassifier : referencedClassifierArray) {
Assert.isTrue(
expectedEntityType.equals(referencedClassifier.getType()),
"Entity type `" + referencedClassifier.getType() + "` in passed argument " +
"doesn't match the referenced entity type: `" + expectedEntityType + "`!"
);
entityBuilder.setReference(referenceName, referencedClassifier.getPrimaryKey());
}
}
/**
* Returns method implementation that sets the referenced entity by primary key and return no result.
*
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferencedEntityPrimaryKeyWithVoidResult(
@Nonnull ReferenceSchemaContract referenceSchema
) {
final String referenceName = referenceSchema.getName();
final Cardinality cardinality = referenceSchema.getCardinality();
return (proxy, theMethod, args, theState, invokeSuper) -> {
setReferencedEntityPrimaryKey(referenceName, cardinality, args, theState);
return null;
};
}
/**
* Returns method implementation that sets the referenced entity primary key and return the reference to the proxy
* to allow chaining (builder pattern).
*
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferencedEntityPrimaryKeyWithBuilderResult(
@Nonnull ReferenceSchemaContract referenceSchema
) {
final String referenceName = referenceSchema.getName();
final Cardinality cardinality = referenceSchema.getCardinality();
return (proxy, theMethod, args, theState, invokeSuper) -> {
setReferencedEntityPrimaryKey(referenceName, cardinality, args, theState);
return proxy;
};
}
/**
* Sets the referenced entity primary key.
*
* @param referenceName the name of the reference
* @param cardinality the cardinality of the reference
* @param args the arguments passed to the method
* @param theState the state of the sealed entity proxy
*/
private static void setReferencedEntityPrimaryKey(
@Nonnull String referenceName,
@Nonnull Cardinality cardinality,
@Nonnull Object[] args,
@Nonnull SealedEntityProxyState theState
) {
final EntityBuilder entityBuilder = theState.entityBuilder();
final Serializable referencedClassifier = (Serializable) args[0];
if (referencedClassifier == null && (cardinality == Cardinality.ZERO_OR_ONE || cardinality == Cardinality.EXACTLY_ONE)) {
final Collection references = entityBuilder.getReferences(referenceName);
Assert.isPremiseValid(references.size() < 2, "Cardinality is `" + cardinality + "` but there are more than one reference!");
references.forEach(it -> entityBuilder.removeReference(referenceName, it.getReferencedPrimaryKey()));
} else {
final Integer newReferencedPrimaryKey = EvitaDataTypes.toTargetType(referencedClassifier, int.class);
if (cardinality == Cardinality.ZERO_OR_ONE || cardinality == Cardinality.EXACTLY_ONE) {
final Collection references = entityBuilder.getReferences(referenceName);
Assert.isPremiseValid(references.size() < 2, "Cardinality is `" + cardinality + "` but there are more than one reference!");
if (!references.isEmpty()) {
final ReferenceContract singleReference = references.iterator().next();
if (singleReference.getReferencedPrimaryKey() == newReferencedPrimaryKey) {
// do nothing the reference is already set
return;
} else {
// remove existing reference and registered object
entityBuilder.removeReference(referenceName, singleReference.getReferencedPrimaryKey());
}
}
}
entityBuilder.setReference(
referenceName,
newReferencedPrimaryKey
);
}
}
/**
* Returns method implementation that sets the referenced entity primary key and return no result.
*
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferencedEntityPrimaryKeyAsArrayWithBuilderResult(
@Nonnull ReferenceSchemaContract referenceSchema
) {
final String referenceName = referenceSchema.getName();
return (proxy, theMethod, args, theState, invokeSuper) -> {
setReferencedEntityPrimaryKeyAsArray(referenceName, args, theState);
return proxy;
};
}
/**
* Returns method implementation that sets the referenced entity primary key and return the reference to the proxy
* to allow chaining (builder pattern).
*
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferencedEntityPrimaryKeyAsArrayWithVoidResult(
@Nonnull ReferenceSchemaContract referenceSchema
) {
final String referenceName = referenceSchema.getName();
return (proxy, theMethod, args, theState, invokeSuper) -> {
setReferencedEntityPrimaryKeyAsArray(referenceName, args, theState);
return proxy;
};
}
/**
* Sets the referenced entity primary key as an array.
*
* @param referenceName the name of the reference
* @param args an array of objects containing the primary key values
* @param theState the state of the sealed entity proxy
*/
private static void setReferencedEntityPrimaryKeyAsArray(
@Nonnull String referenceName,
@Nonnull Object[] args,
@Nonnull SealedEntityProxyState theState
) {
final EntityBuilder entityBuilder = theState.entityBuilder();
final int length = Array.getLength(args[0]);
for (int i = 0; i < length; i++) {
final Serializable primaryKey = (Serializable) Array.get(args[0], i);
entityBuilder.setReference(
referenceName, EvitaDataTypes.toTargetType(primaryKey, int.class)
);
}
}
/**
* Returns method implementation that sets the referenced entity by extracting it from {@link EntityClassifier}
* and return no result.
*
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferencedEntityPrimaryKeyAsCollectionWithBuilderResult(
@Nonnull ReferenceSchemaContract referenceSchema
) {
final String referenceName = referenceSchema.getName();
return (proxy, theMethod, args, theState, invokeSuper) -> {
setReferencedEntityPrimaryKeyAsCollection(referenceName, args, theState);
return proxy;
};
}
/**
* Returns method implementation that sets the referenced entity primary key and return the reference to the proxy
* to allow chaining (builder pattern).
*
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferencedEntityPrimaryKeyAsCollectionWithVoidResult(
@Nonnull ReferenceSchemaContract referenceSchema
) {
final String referenceName = referenceSchema.getName();
return (proxy, theMethod, args, theState, invokeSuper) -> {
setReferencedEntityPrimaryKeyAsCollection(referenceName, args, theState);
return proxy;
};
}
/**
* Sets the referenced entity primary key as a collection.
*
* @param referenceName the name of the reference schema
* @param args the arguments passed to the method
* @param theState the state of the sealed entity proxy
*/
private static void setReferencedEntityPrimaryKeyAsCollection(
@Nonnull String referenceName,
@Nonnull Object[] args,
@Nonnull SealedEntityProxyState theState
) {
final EntityBuilder entityBuilder = theState.entityBuilder();
// noinspection unchecked
final Collection primaryKeys = (Collection) args[0];
for (Serializable primaryKey : primaryKeys) {
entityBuilder.setReference(referenceName, EvitaDataTypes.toTargetType(primaryKey, int.class));
}
}
/**
* Returns method implementation that sets the referenced entity by extracting it from {@link SealedEntityProxy}
* and return no result.
*
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferencedEntityWithVoidResult(
@Nonnull ReferenceSchemaContract referenceSchema
) {
final String referenceName = referenceSchema.getName();
final String expectedEntityType = getReferencedType(referenceSchema, true);
final Cardinality cardinality = referenceSchema.getCardinality();
return (proxy, theMethod, args, theState, invokeSuper) -> {
setReferencedEntity(referenceName, expectedEntityType, cardinality, args, theState);
return null;
};
}
/**
* Returns method implementation that sets the referenced entity by extracting it from {@link SealedEntityProxy}
* and return the reference to the proxy to allow chaining (builder pattern).
*
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferencedEntityWithBuilderResult(
@Nonnull ReferenceSchemaContract referenceSchema
) {
final String referenceName = referenceSchema.getName();
final String expectedEntityType = getReferencedType(referenceSchema, true);
final Cardinality cardinality = referenceSchema.getCardinality();
return (proxy, theMethod, args, theState, invokeSuper) -> {
setReferencedEntity(referenceName, expectedEntityType, cardinality, args, theState);
return proxy;
};
}
/**
* Sets the referenced entity by extracting it from {@link SealedEntityProxy} and returns the reference to the proxy
* to allow chaining (builder pattern).
*
* @param referenceName the name of the reference
* @param expectedEntityType the expected entity type
* @param cardinality the cardinality of the reference
* @param args the arguments passed to the method
* @param theState the state of the SealedEntityProxy
*/
private static void setReferencedEntity(
@Nonnull String referenceName,
@Nonnull String expectedEntityType,
@Nonnull Cardinality cardinality,
@Nonnull Object[] args,
@Nonnull SealedEntityProxyState theState
) {
final EntityBuilder entityBuilder = theState.entityBuilder();
final SealedEntityProxy referencedEntity = (SealedEntityProxy) args[0];
if (referencedEntity == null && (cardinality == Cardinality.ZERO_OR_ONE || cardinality == Cardinality.EXACTLY_ONE)) {
final Collection references = entityBuilder.getReferences(referenceName);
Assert.isPremiseValid(references.size() < 2, "Cardinality is `" + cardinality + "` but there are more than one reference!");
references.forEach(it -> entityBuilder.removeReference(referenceName, it.getReferencedPrimaryKey()));
} else {
final Collection references = entityBuilder.getReferences(referenceName);
if (cardinality == Cardinality.ZERO_OR_ONE || cardinality == Cardinality.EXACTLY_ONE) {
Assert.isPremiseValid(references.size() < 2, "Cardinality is `" + cardinality + "` but there are more than one reference!");
if (!references.isEmpty()) {
final ReferenceContract singleReference = references.iterator().next();
if (singleReference.getReferencedPrimaryKey() == referencedEntity.getPrimaryKey()) {
// just exchange registered object - the set entity and existing reference share same primary key
theState.registerReferencedEntityObject(expectedEntityType, singleReference.getReferencedPrimaryKey(), referencedEntity, ProxyType.REFERENCED_ENTITY);
return;
} else {
// remove existing reference and registered object
entityBuilder.removeReference(referenceName, singleReference.getReferencedPrimaryKey());
theState.unregisterReferencedEntityObject(expectedEntityType, singleReference.getReferencedPrimaryKey(), ProxyType.REFERENCED_ENTITY);
}
}
}
final EntityContract sealedEntity = referencedEntity.entity();
Assert.isTrue(
expectedEntityType.equals(sealedEntity.getType()),
"Entity type `" + sealedEntity.getType() + "` in passed argument " +
"doesn't match the referencedEntity entity type: `" + expectedEntityType + "`!"
);
final Integer primaryKey = sealedEntity.getPrimaryKey();
entityBuilder.setReference(referenceName, primaryKey);
theState.registerReferencedEntityObject(expectedEntityType, primaryKey, referencedEntity, ProxyType.REFERENCED_ENTITY);
}
}
/**
* Returns method implementation that sets the referenced entity by extracting it from {@link SealedEntityProxy}
* and return no result.
*
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferencedEntityAsArrayWithBuilderResult(
@Nonnull ReferenceSchemaContract referenceSchema
) {
final String referenceName = referenceSchema.getName();
final String expectedEntityType = getReferencedType(referenceSchema, true);
return (proxy, theMethod, args, theState, invokeSuper) -> {
setReferencedEntityAsArray(referenceName, expectedEntityType, args, theState);
return proxy;
};
}
/**
* Returns method implementation that sets the referenced entity by extracting it from {@link SealedEntityProxy}
* and return the reference to the proxy to allow chaining (builder pattern).
*
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferencedEntityAsArrayWithVoidResult(
@Nonnull ReferenceSchemaContract referenceSchema
) {
final String referenceName = referenceSchema.getName();
final String expectedEntityType = getReferencedType(referenceSchema, true);
return (proxy, theMethod, args, theState, invokeSuper) -> {
setReferencedEntityAsArray(referenceName, expectedEntityType, args, theState);
return proxy;
};
}
/**
* Sets the referenced entity as an array by extracting it from the SealedEntityProxy.
*
* @param referenceName the name of the reference
* @param expectedEntityType the expected type of the referenced entity
* @param args the arguments passed to the method
* @param theState the SealedEntityProxy state
*/
private static void setReferencedEntityAsArray(
@Nonnull String referenceName,
@Nonnull String expectedEntityType,
@Nonnull Object[] args,
@Nonnull SealedEntityProxyState theState
) {
final EntityBuilder entityBuilder = theState.entityBuilder();
final Object[] referencedArray = (Object[]) args[0];
for (Object referencedEntity : referencedArray) {
final EntityContract sealedEntity = ((SealedEntityProxy) referencedEntity).entity();
Assert.isTrue(
expectedEntityType.equals(sealedEntity.getType()),
"Entity type `" + sealedEntity.getType() + "` in passed argument " +
"doesn't match the referenced entity type: `" + expectedEntityType + "`!"
);
entityBuilder.setReference(referenceName, sealedEntity.getPrimaryKey());
}
}
/**
* Returns method implementation that sets the referenced entity by extracting it from {@link SealedEntityProxy}
* and return no result.
*
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferencedEntityAsCollectionWithBuilderResult(
@Nonnull ReferenceSchemaContract referenceSchema
) {
final String referenceName = referenceSchema.getName();
final String expectedEntityType = getReferencedType(referenceSchema, true);
return (proxy, theMethod, args, theState, invokeSuper) -> {
setReferencedEntityAsCollection(referenceName, expectedEntityType, args, theState);
return proxy;
};
}
/**
* Returns method implementation that sets the referenced entity by extracting it from {@link SealedEntityProxy}
* and return the reference to the proxy to allow chaining (builder pattern).
*
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferencedEntityAsCollectionWithVoidResult(
@Nonnull ReferenceSchemaContract referenceSchema
) {
final String referenceName = referenceSchema.getName();
final String expectedEntityType = getReferencedType(referenceSchema, true);
return (proxy, theMethod, args, theState, invokeSuper) -> {
setReferencedEntityAsCollection(referenceName, expectedEntityType, args, theState);
return proxy;
};
}
/**
* Sets the referenced entity as a collection by extracting it from {@link SealedEntityProxy}.
*
* @param referenceName the name of the reference
* @param expectedEntityType the expected entity type
* @param args the arguments passed to the method
* @param theState the state of the sealed entity proxy
*/
private static void setReferencedEntityAsCollection(
@Nonnull String referenceName,
@Nonnull String expectedEntityType,
@Nonnull Object[] args,
@Nonnull SealedEntityProxyState theState
) {
final EntityBuilder entityBuilder = theState.entityBuilder();
//noinspection unchecked
final Collection referencedArray = (Collection) args[0];
for (SealedEntityProxy referencedEntity : referencedArray) {
final EntityContract sealedEntity = referencedEntity.entity();
Assert.isTrue(
expectedEntityType.equals(sealedEntity.getType()),
"Entity type `" + sealedEntity.getType() + "` in passed argument " +
"doesn't match the referenced entity type: `" + expectedEntityType + "`!"
);
entityBuilder.setReference(referenceName, sealedEntity.getPrimaryKey());
}
}
/**
* Returns method implementation that removes the referenced entity id and return no result.
*
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler removeReferencedEntityIdWithVoidResult(
@Nonnull ReferenceSchemaContract referenceSchema
) {
final String referenceName = referenceSchema.getName();
return (proxy, theMethod, args, theState, invokeSuper) -> {
removeReferencedEntityId(referenceName, args, theState);
return null;
};
}
/**
* Returns method implementation that removes the referenced entity id and return TRUE if the reference was removed.
*
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler removeReferencedEntityIdWithBooleanResult(
@Nonnull ReferenceSchemaContract referenceSchema
) {
final String referenceName = referenceSchema.getName();
return (proxy, theMethod, args, theState, invokeSuper) -> removeReferencedEntityId(referenceName, args, theState);
}
/**
* Returns method implementation that removes the referenced entity id and return the reference to the proxy to allow
* chaining (builder pattern).
*
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler removeReferencedEntityIdWithBuilderResult(
@Nonnull ReferenceSchemaContract referenceSchema
) {
final String referenceName = referenceSchema.getName();
return (proxy, theMethod, args, theState, invokeSuper) -> {
removeReferencedEntityId(referenceName, args, theState);
return proxy;
};
}
/**
* Removes the referenced entity id from the entity builder stored in the state.
*
* @param referenceName the name of the reference
* @param args the arguments passed to the method
* @param theState the state of the entity proxy
*/
private static boolean removeReferencedEntityId(
@Nonnull String referenceName,
@Nonnull Object[] args,
@Nonnull SealedEntityProxyState theState
) {
final EntityBuilder entityBuilder = theState.entityBuilder();
final Serializable referencedPrimaryKey = (Serializable) args[0];
final Integer referenceId = EvitaDataTypes.toTargetType(referencedPrimaryKey, int.class);
final Optional reference = entityBuilder.getReference(referenceName, referenceId);
if (reference.isPresent()) {
entityBuilder.removeReference(
referenceName,
referenceId
);
}
return reference.isPresent();
}
/**
* Returns method implementation that removes the referenced entity id and return the reference to the removed
* reference proxy.
*
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler removeReferencedEntityIdWithReferenceResult(
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull Class> expectedType,
boolean entityRecognizedInReturnType
) {
final String referenceName = referenceSchema.getName();
return (proxy, theMethod, args, theState, invokeSuper) -> {
final EntityBuilder entityBuilder = theState.entityBuilder();
final int referencedPrimaryKey = EvitaDataTypes.toTargetType((Serializable) args[0], int.class);
final Optional reference = entityBuilder.getReference(referenceName, referencedPrimaryKey);
if (reference.isPresent()) {
entityBuilder.removeReference(referenceName, referencedPrimaryKey);
if (entityRecognizedInReturnType) {
if (reference.get().getReferencedEntity().isPresent()) {
final SealedEntity referencedEntity = reference.get().getReferencedEntity().get();
return theState.getOrCreateReferencedEntityProxy(
expectedType, referencedEntity, ProxyType.REFERENCED_ENTITY
);
} else {
throw ContextMissingException.referencedEntityContextMissing(theState.getType(), referenceName);
}
} else {
return theState.getOrCreateEntityReferenceProxy(expectedType, reference.get());
}
} else {
return null;
}
};
}
/**
* Returns method implementation that creates or updates reference by passing integer id and a consumer lambda, that
* could immediately set attributes on the reference.
*
* @param proxyState the proxy state
* @param method the method
* @param returnType the return type
* @param referenceSchema the reference schema
* @param referencedIdIndex the index of the referenced id parameter
* @param consumerIndex the index of the consumer parameter
* @param expectedType the expected type of the referenced entity proxy
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferenceByIdAndReferenceConsumer(
@Nonnull SealedEntityProxyState proxyState,
@Nonnull Method method,
@Nonnull Class> returnType,
@Nonnull ReferenceSchemaContract referenceSchema,
int referencedIdIndex,
int consumerIndex,
@Nonnull Class> expectedType
) {
if (method.isAnnotationPresent(CreateWhenMissing.class) ||
Arrays.stream(method.getParameterAnnotations()[consumerIndex]).anyMatch(CreateWhenMissing.class::isInstance)) {
if (returnType.equals(proxyState.getProxyClass())) {
return getOrCreateReferenceWithIdAndEntityBuilderResult(
referenceSchema, expectedType,
referencedIdIndex, consumerIndex
);
} else {
return getOrCreateReferenceWithIdAndVoidResult(
referenceSchema, expectedType,
referencedIdIndex, consumerIndex
);
}
} else {
if (returnType.equals(proxyState.getProxyClass())) {
return getAndUpdateReferenceWithIdAndEntityBuilderResult(
referenceSchema, expectedType,
referencedIdIndex, consumerIndex
);
} else {
return getAndUpdateReferenceWithIdAndVoidResult(
referenceSchema, expectedType,
referencedIdIndex, consumerIndex
);
}
}
}
/**
* Returns method implementation that creates or updates reference by passing integer id and a consumer lambda, that
* could immediately modify referenced entity. This method creates the reference without possibility to set
* attributes on the reference and works directly with the referenced entity.
*
* @param proxyState the proxy state
* @param returnType the return type
* @param referenceSchema the reference schema
* @param referencedIdIndex the index of the referenced id parameter
* @param consumerIndex the index of the consumer parameter
* @param expectedType the expected type of the referenced entity proxy
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferenceByIdAndEntityConsumer(
@Nonnull SealedEntityProxyState proxyState,
@Nonnull Class> returnType,
@Nonnull ReferenceSchemaContract referenceSchema,
int referencedIdIndex,
int consumerIndex,
@Nonnull Class> expectedType
) {
if (returnType.equals(proxyState.getProxyClass())) {
return createReferencedEntityWithIdAndEntityBuilderResult(
referenceSchema, expectedType,
referencedIdIndex, consumerIndex
);
} else {
return createReferencedEntityWithIdAndVoidResult(
referenceSchema, expectedType,
referencedIdIndex, consumerIndex
);
}
}
/**
* Return a method implementation that creates new proxy object representing a reference to and external entity
* and returns the reference to the created proxy allowing to set reference properties on it.
*
* @param referenceSchema the reference schema to use
* @param expectedType the expected type of the referenced entity proxy
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setOrRemoveReferenceByEntityReturnType(
@Nonnull Method method,
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull Class> expectedType
) {
if (method.isAnnotationPresent(RemoveWhenExists.class)) {
return removeReferencedEntityIdWithReferenceResult(
referenceSchema, expectedType, true
);
} else {
return (proxy, theMethod, args, theState, invokeSuper) -> {
final EntityBuilder entityBuilder = theState.entityBuilder();
final int referencedId = EvitaDataTypes.toTargetType((Serializable) args[0], int.class);
entityBuilder.setReference(referenceSchema.getName(), referencedId);
return theState.createEntityReferenceProxy(
theState.getEntitySchema(),
referenceSchema,
expectedType,
ProxyType.REFERENCE,
referencedId
);
};
}
}
/**
* Returns method implementation that creates or updates reference by consumer lambda, that
* could immediately modify referenced entity. This method creates the reference without possibility to set
* attributes on the reference and works directly with the referenced entity. The referenced entity has no primary
* key, which is assigned later when the entity is persisted.
*
* @param method the method
* @param proxyState the proxy state
* @param referenceSchema the reference schema
* @param returnType the return type
* @param expectedType the expected type of the referenced entity proxy
* @return the method implementation
*/
@Nullable
private static CurriedMethodContextInvocationHandler setReferenceByEntityConsumer(
@Nonnull Method method,
@Nonnull SealedEntityProxyState proxyState,
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull Class> returnType,
@Nonnull Class> expectedType
) {
final String referencedEntityType = getReferencedType(referenceSchema, true);
return proxyState.getEntitySchema(referencedEntityType)
.map(referencedEntitySchema -> {
if (method.isAnnotationPresent(CreateWhenMissing.class) ||
Arrays.stream(method.getParameterAnnotations()[0]).anyMatch(CreateWhenMissing.class::isInstance)) {
if (returnType.equals(proxyState.getProxyClass())) {
return createReferencedEntityWithEntityBuilderResult(
referencedEntitySchema,
referenceSchema,
expectedType
);
} else if (void.class.equals(returnType)) {
return createReferencedEntityWithVoidResult(
referencedEntitySchema,
referenceSchema,
expectedType
);
} else {
return null;
}
} else {
if (returnType.equals(proxyState.getProxyClass())) {
return updateReferencedEntityWithEntityBuilderResult(
referenceSchema,
expectedType
);
} else if (void.class.equals(returnType)) {
return updateReferencedEntityWithVoidResult(
referenceSchema,
expectedType
);
} else {
return null;
}
}
})
.orElse(null);
}
/**
* Returns method implementation that creates or updates reference by passing directly the entities fetched from
* other sources. This implementation doesn't allow to set attributes on the reference.
*
* @param proxyState the proxy state
* @param entityRecognizedIn the entity recognized in
* @param returnType the return type
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferenceByEntity(
@Nonnull SealedEntityProxyState proxyState,
@Nonnull RecognizedContext entityRecognizedIn,
@Nonnull Class> returnType,
@Nonnull ReferenceSchemaContract referenceSchema
) {
final ResolvedParameter referencedParameter = entityRecognizedIn.entityContract();
if (referencedParameter.mainType().isArray()) {
if (returnType.equals(proxyState.getProxyClass())) {
return setReferencedEntityAsArrayWithBuilderResult(referenceSchema);
} else {
return setReferencedEntityAsArrayWithVoidResult(referenceSchema);
}
} else if (Collection.class.isAssignableFrom(referencedParameter.mainType())) {
if (returnType.equals(proxyState.getProxyClass())) {
return setReferencedEntityAsCollectionWithBuilderResult(referenceSchema);
} else {
return setReferencedEntityAsCollectionWithVoidResult(referenceSchema);
}
} else {
if (returnType.equals(proxyState.getProxyClass())) {
return setReferencedEntityWithBuilderResult(referenceSchema);
} else {
return setReferencedEntityWithVoidResult(referenceSchema);
}
}
}
/**
* Returns method implementation that creates or updates reference by passing {@link EntityClassifier} instances
* to the method parameter. This implementation doesn't allow to set attributes on the reference.
*
* @param proxyState the proxy state
* @param entityRecognizedIn the entity recognized in
* @param returnType the return type
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferenceByEntityClassifier(
@Nonnull SealedEntityProxyState proxyState,
@Nonnull RecognizedContext entityRecognizedIn,
@Nonnull Class> returnType,
@Nonnull ReferenceSchemaContract referenceSchema
) {
final ResolvedParameter referencedParameter = entityRecognizedIn.entityContract();
if (referencedParameter.mainType().isArray()) {
if (returnType.equals(proxyState.getProxyClass())) {
return setReferencedEntityClassifierAsArrayWithBuilderResult(referenceSchema);
} else {
return setReferencedEntityClassifierAsArrayWithVoidResult(referenceSchema);
}
} else if (Collection.class.isAssignableFrom(referencedParameter.mainType)) {
if (returnType.equals(proxyState.getProxyClass())) {
return setReferencedEntityClassifierAsCollectionWithBuilderResult(referenceSchema);
} else {
return setReferencedEntityClassifierAsCollectionWithVoidResult(referenceSchema);
}
} else {
if (returnType.equals(proxyState.getProxyClass())) {
return setReferencedEntityClassifierWithBuilderResult(referenceSchema);
} else {
return setReferencedEntityClassifierWithVoidResult(referenceSchema);
}
}
}
/**
* Returns method implementation that creates or updates reference by passing primary keys
* to the method parameter. This implementation doesn't allow to set attributes on the reference.
*
* @param proxyState the proxy state
* @param referencedParameter the entity recognized in
* @param returnType the return type
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setReferenceById(
@Nonnull SealedEntityProxyState proxyState,
@Nonnull ResolvedParameter referencedParameter,
@Nonnull Class> returnType,
@Nonnull ReferenceSchemaContract referenceSchema
) {
if (referencedParameter.mainType().isArray()) {
if (returnType.equals(proxyState.getProxyClass())) {
return setReferencedEntityPrimaryKeyAsArrayWithBuilderResult(referenceSchema);
} else {
return setReferencedEntityPrimaryKeyAsArrayWithVoidResult(referenceSchema);
}
} else if (Collection.class.isAssignableFrom(referencedParameter.mainType)) {
if (returnType.equals(proxyState.getProxyClass())) {
return setReferencedEntityPrimaryKeyAsCollectionWithBuilderResult(referenceSchema);
} else {
return setReferencedEntityPrimaryKeyAsCollectionWithVoidResult(referenceSchema);
}
} else {
if (returnType.equals(proxyState.getProxyClass())) {
return setReferencedEntityPrimaryKeyWithBuilderResult(referenceSchema);
} else {
return setReferencedEntityPrimaryKeyWithVoidResult(referenceSchema);
}
}
}
/**
* Returns method implementation that creates, updates or removes reference by passing referenced entity ids.
* This implementation doesn't allow to set attributes on the reference.
*
* @param proxyState the proxy state
* @param method the method
* @param returnType the return type
* @param referenceSchema the reference schema
* @return the method implementation
*/
@Nonnull
private static CurriedMethodContextInvocationHandler setOrRemoveReferenceById(
@Nonnull SealedEntityProxyState proxyState,
@Nonnull Method method,
@Nonnull Class> returnType,
@Nonnull ReferenceSchemaContract referenceSchema
) {
if (method.isAnnotationPresent(RemoveWhenExists.class)) {
if (returnType.equals(proxyState.getProxyClass())) {
return removeReferencedEntityIdWithBuilderResult(referenceSchema);
} else if (void.class.equals(returnType)) {
return removeReferencedEntityIdWithVoidResult(referenceSchema);
} else if (boolean.class.equals(returnType)) {
return removeReferencedEntityIdWithBooleanResult(referenceSchema);
} else {
return removeReferencedEntityIdWithReferenceResult(
referenceSchema, returnType, false
);
}
} else {
if (returnType.equals(proxyState.getProxyClass())) {
return setReferencedEntityPrimaryKeyWithBuilderResult(referenceSchema);
} else if (void.class.equals(returnType)) {
return setReferencedEntityPrimaryKeyWithVoidResult(referenceSchema);
} else if (method.isAnnotationPresent(CreateWhenMissing.class)) {
return getOrCreateByIdWithReferenceResult(referenceSchema, returnType);
} else {
return null;
}
}
}
/**
* Returns parsed arguments for a method that creates, updates or removes a reference by passing referenced entity ids.
*
* @param method the method to be invoked
* @param proxyState the proxy state of the entity
* @param reflectionLookup the reflection lookup utility
* @param referenceSchema the reference schema for the entity
* @param firstParameter the first parameter of the method
* @return the parsed arguments
*/
@Nonnull
private static ParsedArguments getParsedArguments(
@Nonnull Method method,
@Nonnull SealedEntityProxyState proxyState,
@Nonnull ReflectionLookup reflectionLookup,
@Nonnull ReferenceSchemaContract referenceSchema,
@Nonnull ResolvedParameter firstParameter
) {
OptionalInt referencedIdIndex = OptionalInt.empty();
OptionalInt consumerIndex = OptionalInt.empty();
Optional referencedType = empty();
for (int i = 0; i < method.getParameterCount(); i++) {
final Class> parameterType = method.getParameterTypes()[i];
if (NumberUtils.isIntConvertibleNumber(parameterType)) {
referencedIdIndex = OptionalInt.of(i);
} else if (Consumer.class.isAssignableFrom(parameterType)) {
consumerIndex = OptionalInt.of(i);
final List genericType = GenericsUtils.getGenericType(proxyState.getProxyClass(), method.getGenericParameterTypes()[i]);
referencedType = of(
new ResolvedParameter(
method.getParameterTypes()[i],
genericType.get(0).getResolvedType()
)
);
} else if (Collection.class.isAssignableFrom(parameterType)) {
final List genericType = GenericsUtils.getGenericType(proxyState.getProxyClass(), method.getGenericParameterTypes()[i]);
referencedType = of(
new ResolvedParameter(
method.getParameterTypes()[i],
genericType.get(0).getResolvedType()
)
);
} else if (parameterType.isArray()) {
referencedType = of(
new ResolvedParameter(
method.getParameterTypes()[i],
parameterType.getComponentType()
)
);
}
}
@SuppressWarnings("rawtypes") final Class returnType = method.getReturnType();
final Optional entityRecognizedIn = recognizeCallContext(
reflectionLookup, referenceSchema,
of(returnType)
.filter(it -> !void.class.equals(it) && !returnType.equals(proxyState.getProxyClass()))
.orElse(null),
firstParameter,
referencedType.map(ResolvedParameter::resolvedType).orElse(null)
);
return new ParsedArguments(referencedIdIndex, consumerIndex, referencedType, returnType, entityRecognizedIn);
}
/**
* Resolves the first parameter of a method.
*
* @param method the method to be examined
* @param proxyClass
* @return the resolved first parameter, or null if there are no parameters
*/
@Nullable
public static ResolvedParameter resolveFirstParameter(@Nonnull Method method, @Nonnull Class> proxyClass) {
final ResolvedParameter firstParameter;
if (method.getParameterCount() > 0) {
if (Collection.class.isAssignableFrom(method.getParameterTypes()[0])) {
final List genericType = GenericsUtils.getGenericType(proxyClass, method.getGenericParameterTypes()[0]);
firstParameter = new ResolvedParameter(method.getParameterTypes()[0], genericType.get(0).getResolvedType());
} else if (method.getParameterTypes()[0].isArray()) {
firstParameter = new ResolvedParameter(method.getParameterTypes()[0], method.getParameterTypes()[0].getComponentType());
} else {
firstParameter = new ResolvedParameter(method.getParameterTypes()[0], method.getParameterTypes()[0]);
}
} else {
firstParameter = null;
}
return firstParameter;
}
public SetReferenceMethodClassifier() {
super(
"setReference",
(method, proxyState) -> {
final int parameterCount = method.getParameterCount();
// now we need to identify reference schema that is being requested
final ReflectionLookup reflectionLookup = proxyState.getReflectionLookup();
final EntitySchemaContract entitySchema = proxyState.getEntitySchema();
final ReferenceSchemaContract referenceSchema = getReferenceSchema(
method, reflectionLookup, entitySchema
);
if (referenceSchema == null) {
return null;
}
final ResolvedParameter firstParameter = resolveFirstParameter(method, proxyState.getProxyClass());
final ParsedArguments result = getParsedArguments(method, proxyState, reflectionLookup, referenceSchema, firstParameter);
final boolean noDirectlyReferencedEntityRecognized = result.entityRecognizedIn().isEmpty();
final Class> expectedType = result.referencedType().map(ResolvedParameter::resolvedType).orElse(null);
if (parameterCount == 0) {
if (method.isAnnotationPresent(RemoveWhenExists.class)) {
return removeReference(
proxyState, referenceSchema,
result.entityRecognizedIn()
.map(RecognizedContext::entityContract)
.orElseGet(() -> {
final Class> methodReturnType = GenericsUtils.getMethodReturnType(proxyState.getProxyClass(), method);
return new ResolvedParameter(method.getReturnType(), methodReturnType);
}),
isEntityRecognizedIn(result.entityRecognizedIn(), EntityRecognizedIn.RETURN_TYPE)
);
} else if (isEntityRecognizedIn(result.entityRecognizedIn(), EntityRecognizedIn.RETURN_TYPE)) {
//noinspection rawtypes
return getOrCreateWithReferencedEntityResult(
referenceSchema,
result.entityRecognizedIn()
.map(it -> (Class)it.entityContract().resolvedType())
.orElse(result.returnType())
);
} else {
return null;
}
} else if (parameterCount == 1) {
if (result.referencedIdIndex().isPresent() && noDirectlyReferencedEntityRecognized) {
return setOrRemoveReferenceById(proxyState, method, result.returnType(), referenceSchema);
} else if (resolvedTypeIs(result.referencedType(), EntityClassifier.class) && noDirectlyReferencedEntityRecognized) {
return setReferenceByEntityClassifier(proxyState, result.entityRecognizedIn().orElseThrow(), result.returnType(), referenceSchema);
} else if (resolvedTypeIsNumber(result.referencedType()) && noDirectlyReferencedEntityRecognized) {
return setReferenceById(proxyState, result.referencedType().orElseThrow(), result.returnType(), referenceSchema);
} else if (isEntityRecognizedIn(result.entityRecognizedIn(), EntityRecognizedIn.PARAMETER)) {
return setReferenceByEntity(proxyState, result.entityRecognizedIn().orElseThrow(), result.returnType(), referenceSchema);
} else if (isEntityRecognizedIn(result.entityRecognizedIn(), EntityRecognizedIn.CONSUMER)) {
return setReferenceByEntityConsumer(method, proxyState, referenceSchema, result.returnType(), expectedType);
} else if (isEntityRecognizedIn(result.entityRecognizedIn(), EntityRecognizedIn.RETURN_TYPE) && result.referencedIdIndex().isPresent()) {
return setOrRemoveReferenceByEntityReturnType(method, referenceSchema, result.entityRecognizedIn().get().entityContract().resolvedType());
}
} else if (parameterCount == 2 && result.consumerIndex().isPresent() && result.referencedIdIndex().isPresent()) {
if (isEntityRecognizedIn(result.entityRecognizedIn(), EntityRecognizedIn.CONSUMER)) {
return setReferenceByIdAndEntityConsumer(
proxyState, result.returnType(), referenceSchema,
result.referencedIdIndex().getAsInt(),
result.consumerIndex().getAsInt(),
expectedType
);
} else if (noDirectlyReferencedEntityRecognized) {
return setReferenceByIdAndReferenceConsumer(
proxyState, method, result.returnType(), referenceSchema,
result.referencedIdIndex().getAsInt(),
result.consumerIndex().getAsInt(),
expectedType
);
}
}
return null;
}
);
}
/**
* Represents identification of the place which has been recognized as reference representation.
*/
public enum EntityRecognizedIn {
PARAMETER,
CONSUMER,
RETURN_TYPE
}
/**
* ParsedArguments is a record class that represents the parsed arguments for a method call.
* It contains optional indexes for the referencedId and consumer, optional resolved type for the referencedType,
* a Class object for the returnType, and an optional RecognizedContext object for the entityRecognizedIn.
*
* @param referencedIdIndex the index of the referenced id parameter
* @param consumerIndex the index of the consumer parameter
* @param referencedType the resolved type of the referenced entity
* @param returnType the return type
* @param entityRecognizedIn the context in which the entity is recognized
*/
private record ParsedArguments(
@Nonnull OptionalInt referencedIdIndex,
@Nonnull OptionalInt consumerIndex,
@Nonnull Optional referencedType,
@Nonnull Class> returnType,
@Nonnull Optional entityRecognizedIn
) {
}
/**
* Record containing information about the resolved generic parameter type along with the original class.
*
* @param mainType common parameter type (generic wrapper)
* @param resolvedType resolved generic parameter type
*/
public record ResolvedParameter(
@Nonnull Class> mainType,
@Nonnull Class> resolvedType
) {
}
/**
* Record containing information about the context recognized from the method signature.
*
* @param recognizedIn the place where the entity type has been recognized
* @param entityContract the resolved entity contract
* @param entityType the resolved entity type
* @param isGroup whether the entity is a reference group or referenced entity itself
*/
public record RecognizedContext(
@Nonnull EntityRecognizedIn recognizedIn,
@Nonnull ResolvedParameter entityContract,
@Nonnull String entityType,
boolean isGroup
) {
}
}