/** * Get an attribute from an object. * * @param entity - The object which owns the attribute. * @param attributeName - name of the attribute. * @param scope - contains request level metadata. * @return the value of the attribute */ default Object getAttribute(Object entity, String attributeName, RequestScope scope) { RequestScope requestScope; try { requestScope = scope; } catch (ClassCastException e) { throw new ClassCastException("Fail trying to cast requestscope"); } Object val = PersistentResource.getValue(entity, attributeName, requestScope); return val; }
/** * No-op transaction, do nothing. * @param entityClass the type of class to load * @param id - the ID of the object to load. * @param filterExpression - security filters that can be evaluated in the data store. * @param scope - the current request scope. It is optional for the data store to attempt evaluation. * @return a new persistent resource with a new instance of {@code entityClass} */ @Override public Object loadObject(Class<?> entityClass, Serializable id, Optional<FilterExpression> filterExpression, RequestScope scope) { // Loads are supported but empty object (with specified id) is returned. // NOTE: This is primarily useful for enabling objects of solely computed properties to be fetched. Object entity; try { entity = entityClass.newInstance(); } catch (IllegalAccessException | InstantiationException e) { log.error("Could not load object {} through NoopStore", entityClass, e); throw new RuntimeException(e); } String uuid = scope.getUUIDFor(entity); // Side-effecting method of the PersistentResource :( however, it enables us to do this without reinventing // the wheel. Should probably be refactored eventually nonetheless. new PersistentResource<>(entity, null, uuid, scope).setId(id.toString()); return entity; }
/** * Get attributes mapping from entity. * * @return Mapping of attributes to objects */ protected Map<String, Object> getAttributes() { final Map<String, Object> attributes = new LinkedHashMap<>(); final Set<String> attrFields = filterFields(dictionary.getAttributes(obj)); for (String field : attrFields) { Object val = getAttribute(field); attributes.put(field, val); } return attributes; }
/** * Get collection of resources from relation field. * * @param relationName field * @return collection relation */ public Set<PersistentResource> getRelationCheckedFiltered(String relationName, Optional<FilterExpression> filterExpression, Optional<Sorting> sorting, Optional<Pagination> pagination) { return filter(ReadPermission.class, getRelation(relationName, filterExpression, sorting, pagination, true)); }
/** * Nulls the relationship or attribute and checks update permissions. * Invokes the set[fieldName] method on the target object OR set the field with the corresponding name. * @param fieldName the field name to set or invoke equivalent set method * @param oldValue the old value */ protected void nullValue(String fieldName, PersistentResource oldValue) { if (oldValue == null) { return; } String inverseField = getInverseRelationField(fieldName); if (!inverseField.isEmpty()) { oldValue.checkFieldAwareDeferPermissions(UpdatePermission.class, inverseField, null, getObject()); } this.setValueChecked(fieldName, null); }
@Override public boolean equals(Object obj) { if (obj instanceof PersistentResource) { PersistentResource that = (PersistentResource) obj; if (this.getObject() == that.getObject()) { return true; } String theirId = dictionary.getId(that.getObject()); return this.matchesId(theirId) && Objects.equals(this.type, that.type); } return false; }
PersistentResource<T> newResource = new PersistentResource<>(obj, parent, id, requestScope); assignId(newResource, id); checkPermission(CreatePermission.class, newResource); newResource.auditClass(Audit.Action.CREATE, new ChangeSpec(newResource, null, null, newResource.getObject())); String type = newResource.getType(); requestScope.setUUIDForObject(type, id, newResource.getObject()); .filter(relationName -> newResource.getRelationshipType(relationName).isToMany() && newResource.getValueUnchecked(relationName) == null) .forEach(relationName -> newResource.setValue(relationName, new LinkedHashSet<>())); newResource.markDirty(); return newResource;
PersistentResource inverseResource = new PersistentResource(inverseObj, this, uuid, requestScope); Object inverseRelation = inverseResource.getValueUnchecked(inverseName); inverseResource.addToCollection((Collection) inverseRelation, inverseName, this); } else { inverseResource.setValueChecked(inverseName, Collections.singleton(this.getObject())); } else if (inverseType.isAssignableFrom(this.getResourceClass())) { inverseResource.setValueChecked(inverseName, this.getObject()); } else { throw new InternalServerErrorException("Relationship type mismatch"); inverseResource.markDirty(); RelationshipType inverseRelationType = inverseResource.getRelationshipType(inverseName); if (inverseRelationType.isToOne()) {
String inverseField = getInverseRelationField(relationName); PersistentResource inverseResource = new PersistentResource(inverseEntity, this, uuid, requestScope); Object inverseRelation = inverseResource.getValueUnchecked(inverseField); inverseResource.delFromCollection((Collection) inverseRelation, inverseField, this, true); } else if (inverseType.isAssignableFrom(this.getResourceClass())) { inverseResource.nullValue(inverseField, this); } else { throw new InternalServerErrorException("Relationship type mismatch"); inverseResource.markDirty(); RelationshipType inverseRelationType = inverseResource.getRelationshipType(inverseField); if (inverseRelationType.isToOne()) {
Optional<String> filters, boolean generateTotals) { EntityDictionary dictionary = parentResource.getRequestScope().getDictionary(); Class entityClass = dictionary.getParameterizedType(parentResource.getObject(), fieldName); String typeName = dictionary.getJsonAliasFor(entityClass); Optional<FilterExpression> filter = buildFilter(typeName, filters, parentResource.getRequestScope()); relations = parentResource.getRelation(fieldName, ids.get(), filter, sorting, pagination); } else { relations = parentResource.getRelationCheckedFiltered(fieldName, filter, sorting, pagination);
/** * Gets a value from an entity and checks read permissions. * @param fieldName the field name * @return value value */ protected Object getValueChecked(String fieldName) { requestScope.publishLifecycleEvent(this, CRUDEvent.CRUDAction.READ); requestScope.publishLifecycleEvent(this, fieldName, CRUDEvent.CRUDAction.READ, Optional.empty()); checkFieldAwareDeferPermissions(ReadPermission.class, fieldName, (Object) null, (Object) null); return getValue(getObject(), fieldName, requestScope); }
@Override public void onNext(CRUDEvent event) { ArrayList<LifeCycleHook> hooks = new ArrayList<>(); //Collect all the hooks that are keyed on a specific field. hooks.addAll(dictionary.getTriggers( event.getResource().getResourceClass(), this.annotation, event.getFieldName())); //Collect all the hooks that are keyed on any field. if (!event.getFieldName().isEmpty()) { hooks.addAll(dictionary.getTriggers(event.getResource().getResourceClass(), this.annotation)); } try { //Invoke all the hooks hooks.forEach((hook) -> { hook.execute( event.getResource().getObject(), event.getResource().getRequestScope(), event.getChanges()); }); } catch (RuntimeException e) { exception = Optional.of(e); if (throwsExceptions) { throw e; } } }
PersistentResource pResource = PersistentResource.createObject( parent.orElse(null), newObjectClass, requestScope, Optional.ofNullable(id)); String fieldName = entry.getKey(); Object val = entry.getValue(); pResource.updateAttribute(fieldName, val); ? null : relationship.toPersistentResources(requestScope); pResource.updateRelation(fieldName, resourceSet);
Optional<Sorting> sorting, Optional<Pagination> pagination) { RelationshipType type = getRelationshipType(relationName); final Class<?> relationClass = dictionary.getParameterizedType(obj, relationName); if (relationClass == null) { throw new InvalidAttributeException(relationName, this.getType()); Optional<FilterExpression> permissionFilter = getPermissionFilterExpression(relationClass, requestScope); Optional<FilterExpression> computedFilters = filterExpression; val = filterInMemory((Collection) val, computedFilters); } else if (type.isToOne()) { resources = new SingleElementSet( new PersistentResource(val, this, requestScope.getUUIDFor(val), requestScope)); } else { resources.add(new PersistentResource(val, this, requestScope.getUUIDFor(val), requestScope));
/** * Retrieve an object without checking read permissions (i.e. value is used internally and not sent to others) * * @param fieldName the field name * @return Value */ protected Object getValueUnchecked(String fieldName) { requestScope.publishLifecycleEvent(this, CRUDEvent.CRUDAction.READ); requestScope.publishLifecycleEvent(this, fieldName, CRUDEvent.CRUDAction.READ, Optional.empty()); return getValue(getObject(), fieldName, requestScope); }
/** * Adds all the relation resources for a given relation path to the included block of the * JsonApiDocument. */ private void addResourcesForPath(JsonApiDocument jsonApiDocument, PersistentResource<?> rec, List<String> relationPath) { //Pop off a relation of relation path String relation = relationPath.remove(0); Optional<FilterExpression> filterExpression = rec.getRequestScope().getExpressionForRelation(rec, relation); Set<PersistentResource> collection; try { collection = rec.getRelationCheckedFiltered(relation, filterExpression, Optional.empty(), Optional.empty()); } catch (ForbiddenAccessException e) { return; } collection.forEach(resource -> { jsonApiDocument.addIncluded(resource.toResource()); //If more relations left in the path, process a level deeper if (!relationPath.isEmpty()) { //Use a copy of the relationPath to preserve the path for remaining branches of the relationship tree addResourcesForPath(jsonApiDocument, resource, new ArrayList<>(relationPath)); } }); }
/** * Audit an action on field. * * @param changeSpec Change spec for audit */ protected void auditField(final ChangeSpec changeSpec) { final String fieldName = changeSpec.getFieldName(); Audit[] annotations = dictionary.getAttributeOrRelationAnnotations(getResourceClass(), Audit.class, fieldName ); if (annotations == null || annotations.length == 0) { // Default to class-level annotation for action auditClass(Audit.Action.UPDATE, changeSpec); return; } for (Audit annotation : annotations) { if (annotation.action().length == 1 && annotation.action()[0] == Audit.Action.UPDATE) { LogMessage message = new LogMessage(annotation, this, Optional.of(changeSpec)); getRequestScope().getAuditLogger().log(message); } else { throw new InvalidSyntaxException("Only Audit.Action.UPDATE is allowed on fields."); } } }
/** * Get relationship mappings. * * @param relationshipFunction a function to load the value of a relationship. Takes a string of the relationship * name and returns the relationship's value. * @return Relationship mapping */ protected Map<String, Relationship> getRelationshipsWithRelationshipFunction( final Function<String, Set<PersistentResource>> relationshipFunction) { final Map<String, Relationship> relationshipMap = new LinkedHashMap<>(); final Set<String> relationshipFields = filterFields(dictionary.getRelationships(obj)); for (String field : relationshipFields) { TreeMap<String, Resource> orderedById = new TreeMap<>(lengthFirstComparator); for (PersistentResource relationship : relationshipFunction.apply(field)) { orderedById.put(relationship.getId(), new ResourceIdentifier(relationship.getType(), relationship.getId()).castToResource()); } Collection<Resource> resources = orderedById.values(); Data<Resource> data; RelationshipType relationshipType = getRelationshipType(field); if (relationshipType.isToOne()) { data = resources.isEmpty() ? new Data<>((Resource) null) : new Data<>(resources.iterator().next()); } else { data = new Data<>(resources); } // TODO - links relationshipMap.put(field, new Relationship(null, data)); } return relationshipMap; }
/** * Get relationship mappings. * * @return Relationship mapping */ protected Map<String, Relationship> getRelationships() { return getRelationshipsWithRelationshipFunction((relationName) -> { Optional<FilterExpression> filterExpression = requestScope.getExpressionForRelation(this, relationName); return getRelationCheckedFiltered(relationName, filterExpression, Optional.empty(), Optional.empty()); }); }
private Set<PersistentResource> getRelationCheckedUnfiltered(String relationName) { return getRelation(relationName, Optional.empty(), Optional.empty(), Optional.empty(), true); }