use of com.yahoo.elide.core.exceptions.InvalidObjectIdentifierException in project elide by yahoo.
the class PersistentResourceFetcher method upsertObject.
/**
* updates or creates existing/new entities.
* @param entity Resource entity
* @param context The request context
* @return {@link PersistentResource} object
*/
private PersistentResource upsertObject(Entity entity, Environment context) {
Set<Entity.Attribute> attributes = entity.getAttributes();
Optional<String> id = entity.getId();
RequestScope requestScope = entity.getRequestScope();
PersistentResource upsertedResource;
EntityDictionary dictionary = requestScope.getDictionary();
PersistentResource parentResource = entity.getParentResource().map(Entity::toPersistentResource).orElse(null);
if (!id.isPresent()) {
// If the ID is generated, it is safe to assign a temporary UUID. Otherwise the client must provide one.
if (dictionary.isIdGenerated(entity.getEntityClass())) {
// Assign a temporary UUID.
entity.setId();
id = entity.getId();
}
upsertedResource = PersistentResource.createObject(parentResource, context.field.getName(), entity.getEntityClass(), requestScope, id);
} else {
try {
Set<PersistentResource> loadedResource = fetchObject(requestScope, entity.getProjection(), Optional.of(Collections.singletonList(id.get()))).getPersistentResources();
upsertedResource = loadedResource.iterator().next();
// The ID doesn't exist yet. Let's create the object.
} catch (InvalidObjectIdentifierException | InvalidValueException e) {
upsertedResource = PersistentResource.createObject(parentResource, context.field.getName(), entity.getEntityClass(), requestScope, id);
}
}
return updateAttributes(upsertedResource, entity, attributes);
}
use of com.yahoo.elide.core.exceptions.InvalidObjectIdentifierException in project elide by yahoo.
the class PersistentResource method loadRecords.
/**
* Load a collection from the datastore.
*
* @param projection the projection to load
* @param requestScope the request scope
* @param ids a list of object identifiers to optionally load. Can be empty.
* @return a filtered collection of resources loaded from the datastore.
*/
public static Observable<PersistentResource> loadRecords(EntityProjection projection, List<String> ids, RequestScope requestScope) {
Type<?> loadClass = projection.getType();
Pagination pagination = projection.getPagination();
Sorting sorting = projection.getSorting();
FilterExpression filterExpression = projection.getFilterExpression();
EntityDictionary dictionary = requestScope.getDictionary();
DataStoreTransaction tx = requestScope.getTransaction();
if (shouldSkipCollection(loadClass, ReadPermission.class, requestScope, projection.getRequestedFields())) {
if (ids.isEmpty()) {
return Observable.empty();
}
throw new InvalidObjectIdentifierException(ids.toString(), dictionary.getJsonAliasFor(loadClass));
}
Set<String> requestedFields = projection.getRequestedFields();
if (pagination != null && !pagination.isDefaultInstance() && !CanPaginateVisitor.canPaginate(loadClass, dictionary, requestScope, requestedFields)) {
throw new BadRequestException(String.format("Cannot paginate %s", dictionary.getJsonAliasFor(loadClass)));
}
Set<PersistentResource> newResources = new LinkedHashSet<>();
if (!ids.isEmpty()) {
String typeAlias = dictionary.getJsonAliasFor(loadClass);
newResources = requestScope.getNewPersistentResources().stream().filter(resource -> typeAlias.equals(resource.getTypeName()) && ids.contains(resource.getUUID().orElse(""))).collect(Collectors.toSet());
FilterExpression idExpression = buildIdFilterExpression(ids, loadClass, dictionary, requestScope);
// Combine filters if necessary
filterExpression = Optional.ofNullable(filterExpression).map(fe -> (FilterExpression) new AndFilterExpression(idExpression, fe)).orElse(idExpression);
}
Optional<FilterExpression> permissionFilter = getPermissionFilterExpression(loadClass, requestScope, requestedFields);
if (permissionFilter.isPresent()) {
if (filterExpression != null) {
filterExpression = new AndFilterExpression(filterExpression, permissionFilter.get());
} else {
filterExpression = permissionFilter.get();
}
}
EntityProjection modifiedProjection = projection.copyOf().filterExpression(filterExpression).sorting(sorting).pagination(pagination).build();
Observable<PersistentResource> existingResources = filter(ReadPermission.class, Optional.ofNullable(modifiedProjection.getFilterExpression()), projection.getRequestedFields(), Observable.fromIterable(new PersistentResourceSet(tx.loadObjects(modifiedProjection, requestScope), requestScope)));
// TODO: Sort again in memory now that two sets are glommed together?
Observable<PersistentResource> allResources = Observable.fromIterable(newResources).mergeWith(existingResources);
Set<String> foundIds = new HashSet<>();
allResources = allResources.doOnNext((resource) -> {
String id = (String) resource.getUUID().orElseGet(resource::getId);
if (ids.contains(id)) {
foundIds.add(id);
}
});
allResources = allResources.doOnComplete(() -> {
Set<String> missedIds = Sets.difference(new HashSet<>(ids), foundIds);
if (!missedIds.isEmpty()) {
throw new InvalidObjectIdentifierException(missedIds.toString(), dictionary.getJsonAliasFor(loadClass));
}
});
return allResources;
}
use of com.yahoo.elide.core.exceptions.InvalidObjectIdentifierException in project elide by yahoo.
the class PersistentResource method loadRecord.
/**
* Load an single entity from the DB.
*
* @param projection What to load from the DB.
* @param id the id
* @param requestScope the request scope
* @param <T> type of resource
* @return resource persistent resource
* @throws InvalidObjectIdentifierException the invalid object identifier exception
*/
@SuppressWarnings("resource")
@NonNull
public static <T> PersistentResource<T> loadRecord(EntityProjection projection, String id, RequestScope requestScope) throws InvalidObjectIdentifierException {
Preconditions.checkNotNull(projection);
Preconditions.checkNotNull(id);
Preconditions.checkNotNull(requestScope);
DataStoreTransaction tx = requestScope.getTransaction();
EntityDictionary dictionary = requestScope.getDictionary();
Type<?> loadClass = projection.getType();
// Check the resource cache if exists
Object obj = requestScope.getObjectById(loadClass, id);
if (obj == null) {
// try to load object
Optional<FilterExpression> permissionFilter = getPermissionFilterExpression(loadClass, requestScope, projection.getRequestedFields());
Type<?> idType = dictionary.getIdType(loadClass);
projection = projection.copyOf().filterExpression(permissionFilter.orElse(null)).build();
obj = tx.loadObject(projection, (Serializable) CoerceUtil.coerce(id, idType), requestScope);
if (obj == null) {
throw new InvalidObjectIdentifierException(id, dictionary.getJsonAliasFor(loadClass));
}
}
PersistentResource<T> resource = new PersistentResource<>((T) obj, requestScope.getUUIDFor(obj), requestScope);
// No need to have read access for a newly created object
if (!requestScope.getNewResources().contains(resource)) {
resource.checkFieldAwarePermissions(ReadPermission.class, projection.getRequestedFields());
}
return resource;
}
use of com.yahoo.elide.core.exceptions.InvalidObjectIdentifierException in project elide by yahoo.
the class PersistentResourceTest method testSuccessfulOneToOneRelationshipAddNull.
/**
* Avoid NPE when PATCH or POST defines relationship with null id
* <pre>
* <code>
* "relationships": {
* "left": {
* "data": {
* "type": "right",
* "id": null
* }
* }
* }
* </code>
* </pre>
*/
@Test
public void testSuccessfulOneToOneRelationshipAddNull() throws Exception {
Left left = new Left();
left.setId(2);
RequestScope goodScope = buildRequestScope(tx, goodUser);
PersistentResource<Left> leftResource = new PersistentResource<>(left, "2", goodScope);
Relationship ids = new Relationship(null, new Data<>(new Resource("right", null, null, null, null, null)));
InvalidObjectIdentifierException thrown = assertThrows(InvalidObjectIdentifierException.class, () -> leftResource.updateRelation("one2one", ids.toPersistentResources(goodScope)));
assertEquals("Unknown identifier null for right", thrown.getMessage());
}
use of com.yahoo.elide.core.exceptions.InvalidObjectIdentifierException in project elide by yahoo.
the class PersistentResource method getRelation.
/**
* Load a relation from the PersistentResource.
*
* @param relationship the relation
* @param ids a list of object identifiers to optionally load. Can be empty.
* @return PersistentResource relation
*/
public Observable<PersistentResource> getRelation(List<String> ids, com.yahoo.elide.core.request.Relationship relationship) {
FilterExpression filterExpression = Optional.ofNullable(relationship.getProjection().getFilterExpression()).orElse(null);
assertPropertyExists(relationship.getName());
Type<?> entityType = dictionary.getParameterizedType(getResourceType(), relationship.getName());
Set<PersistentResource> newResources = new LinkedHashSet<>();
/* If this is a bulk edit request and the ID we are fetching for is newly created... */
if (!ids.isEmpty()) {
// Fetch our set of new resources that we know about since we can't find them in the datastore
newResources = requestScope.getNewPersistentResources().stream().filter(resource -> entityType.isAssignableFrom(resource.getResourceType()) && ids.contains(resource.getUUID().orElse(""))).collect(Collectors.toSet());
FilterExpression idExpression = buildIdFilterExpression(ids, entityType, dictionary, requestScope);
// Combine filters if necessary
filterExpression = Optional.ofNullable(relationship.getProjection().getFilterExpression()).map(fe -> (FilterExpression) new AndFilterExpression(idExpression, fe)).orElse(idExpression);
}
// TODO: Filter on new resources?
// TODO: Update pagination to subtract the number of new resources created?
Observable<PersistentResource> existingResources = filter(ReadPermission.class, Optional.ofNullable(filterExpression), relationship.getProjection().getRequestedFields(), getRelation(relationship.copyOf().projection(relationship.getProjection().copyOf().filterExpression(filterExpression).build()).build(), true));
// TODO: Sort again in memory now that two sets are glommed together?
Observable<PersistentResource> allResources = Observable.fromIterable(newResources).mergeWith(existingResources);
Set<String> foundIds = new HashSet<>();
allResources = allResources.doOnNext((resource) -> {
String id = (String) (resource.getUUID().orElseGet(resource::getId));
if (ids.contains(id)) {
foundIds.add(id);
}
});
allResources = allResources.doOnComplete(() -> {
Set<String> missedIds = Sets.difference(new HashSet<>(ids), foundIds);
if (!missedIds.isEmpty()) {
throw new InvalidObjectIdentifierException(missedIds.toString(), relationship.getName());
}
});
return allResources;
}
Aggregations