| /* |
| * Copyright (c) 2014, 2020 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<String, Node>(); |
| |
| 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 + |
| '}'; |
| } |
| } |
| } |