blob: 498db363ef5a20d4986e094272889213768b0d92 [file] [log] [blame]
/*
* Copyright (c) 2014, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Dmitry Kornilov - Initial implementation
package org.eclipse.persistence.jpa.rs.util;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.internal.jpa.rs.weaving.PersistenceWeavedRest;
import org.eclipse.persistence.jaxb.ObjectGraph;
import org.eclipse.persistence.jaxb.Subgraph;
import org.eclipse.persistence.jpa.rs.PersistenceContext;
import org.eclipse.persistence.jpa.rs.features.fieldsfiltering.FieldsFilter;
import org.eclipse.persistence.jpa.rs.features.fieldsfiltering.FieldsFilterType;
import org.eclipse.persistence.jpa.rs.util.list.PageableCollection;
import org.eclipse.persistence.jpa.rs.util.list.ReadAllQueryResultCollection;
import org.eclipse.persistence.jpa.rs.util.list.ReportQueryResultCollection;
import org.eclipse.persistence.jpa.rs.util.list.SingleResultQueryResult;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.ForeignReferenceMapping;
import jakarta.xml.bind.JAXBElement;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Builds object graph for JPARS 2.0. Object graph defines the structure of the returned data. It hides some
* fields which are not suppose to be returned to the client.
*
* @author Dmitry Kornilov
* @since EclipseLink 2.6.0
*/
public class ObjectGraphBuilder {
private final PersistenceContext context;
private final List<String> RESERVED_WORDS = Arrays.asList("hasMore", "limit", "offset", "links", "count", "items", "_persistence_links");
/**
* Creates an object graph builder.
*
* @param context the persistence context
*/
public ObjectGraphBuilder(PersistenceContext context) {
this.context = context;
}
/**
* Builds object graph for specified object using given filter.
*
* @param object the object to build object graph for. Mandatory.
* @param filter the filter (included or excluded fields) to use. Optional.
* @return constructed object graph.
*/
public ObjectGraph createObjectGraph(Object object, FieldsFilter filter) {
final Node root = new Node();
if (PersistenceWeavedRest.class.isAssignableFrom(object.getClass())) {
createNodeForEntity(object, root);
} else if (object instanceof SingleResultQueryResult) {
root.addAttributeNode("links");
final SingleResultQueryResult singleResultQueryResult = (SingleResultQueryResult)object;
processFieldsList(root.addSubNode("fields"), singleResultQueryResult.getFields());
} else if (object instanceof ReadAllQueryResultCollection) {
createNodeForPageableCollection((PageableCollection<?>) object, root);
} else {
return null;
}
ObjectGraph objectGraph = context.getJAXBContext().createObjectGraph(object.getClass());
fillObjectGraphFromNode(objectGraph, root, filter);
return objectGraph;
}
private void createNodeForPageableCollection(PageableCollection<?> collection, Node node) {
node.addAttributeNode("hasMore");
node.addAttributeNode("count");
node.addAttributeNode("offset");
node.addAttributeNode("limit");
node.addAttributeNode("links");
if (collection.getItems() != null && !collection.getItems().isEmpty()) {
final Node subNode = node.addSubNode("items");
if (collection instanceof ReportQueryResultCollection) {
final ReportQueryResultCollection reportQueryResultCollection = (ReportQueryResultCollection)collection;
processFieldsList(subNode.addSubNode("fields"), reportQueryResultCollection.getItems().get(0).getFields());
} else {
createNodeForEntity(collection.getItems().get(0), subNode);
}
}
}
private void processFieldsList(Node fieldsNode, List<JAXBElement<?>> elements) {
for (JAXBElement<?> field : elements) {
if (field.getValue() instanceof PersistenceWeavedRest) {
final Node subNode = fieldsNode.addSubNode(field.getName().toString());
subNode.addAttributeNode("_persistence_links");
} else {
fieldsNode.addAttributeNode(field.getName().toString());
}
}
}
private void createNodeForEntity(final Object object, final Node node) {
final ClassDescriptor classDescriptor = context.getServerSession().getProject().getDescriptors().get(object.getClass());
if (classDescriptor == null) {
return;
}
node.addAttributeNode("_persistence_links");
for (final DatabaseMapping mapping : classDescriptor.getMappings()) {
if (ForeignReferenceMapping.class.isAssignableFrom(mapping.getClass())) {
final Node subNode = node.addSubNode(mapping.getAttributeName());
if (mapping.isCollectionMapping()) {
// Add CollectionWrapper links and items properties
subNode.addAttributeNode("links");
final Node itemsSubNode = subNode.addSubNode("items");
itemsSubNode.addAttributeNode("_persistence_links");
} else {
// PersistenceWeavedRest._persistence_links
subNode.addAttributeNode("_persistence_links");
}
} else {
node.addAttributeNode(mapping.getAttributeName());
}
}
}
private void fillObjectGraphFromNode(ObjectGraph objectGraph, Node node, FieldsFilter filter) {
for (final String attribute : node.getNodesMap().keySet()) {
if (filter != null) {
if (filter.getType() == FieldsFilterType.INCLUDE) {
if (!(RESERVED_WORDS.contains(attribute) || filter.getFields().contains(attribute))) {
continue;
}
} else {
if (filter.getFields().contains(attribute)) {
continue;
}
}
}
final Node value = node.get(attribute);
if (value != null) {
final Subgraph subgraph = objectGraph.addSubgraph(attribute);
fillSubgraphFromNode(subgraph, value, filter);
} else {
objectGraph.addAttributeNodes(attribute);
}
}
}
private void fillSubgraphFromNode(Subgraph subgraph, Node node, FieldsFilter filter) {
for (final String attribute : node.getNodesMap().keySet()) {
final Node value = node.get(attribute);
if (filter != null) {
if (filter.getType() == FieldsFilterType.INCLUDE) {
if (!(RESERVED_WORDS.contains(attribute) || filter.getFields().contains(attribute))) {
continue;
}
} else {
if (filter.getFields().contains(attribute)) {
continue;
}
}
}
if (value != null) {
final Subgraph childSubgraph = subgraph.addSubgraph(attribute);
fillSubgraphFromNode(childSubgraph, value, filter);
} else {
subgraph.addAttributeNodes(attribute);
}
}
}
/**
* Internal object graph node.
*/
private static class Node {
private final Map<String, Node> nodesMap = new HashMap<>();
public void addAttributeNode(final String attribute) {
nodesMap.put(attribute, null);
}
public Node addSubNode(final String attribute) {
final Node subNode = new Node();
nodesMap.put(attribute, subNode);
return subNode;
}
public Map<String, Node> getNodesMap() {
return nodesMap;
}
public Node get(final String key) {
return nodesMap.get(key);
}
@Override
public String toString() {
return "Node{" +
"nodesMap=" + nodesMap +
'}';
}
}
}