View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors. 
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   *
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.connector.store.jpa.model.simple;
25  
26  import java.util.ArrayList;
27  import java.util.List;
28  import java.util.UUID;
29  import javax.persistence.EntityManager;
30  import javax.persistence.Query;
31  
32  /**
33   * Represents a temporary working area for a query that efficiently retrieves the nodes in a subgraph. This class uses the
34   * database to build up the content of the subgraph, and therefore requires write privilege on the database. The benefit is that
35   * it minimizes the amount of memory required to process the subgraph, plus the set of nodes that make up the subgraph can be
36   * produced with database joins.
37   * <p>
38   * The use of database joins also produces another benefit: the number of SQL statements necessary to build the set of nodes in a
39   * subgraph is equal to the depth of the subgraph, regardless of the number of child nodes at any level.
40   * </p>
41   */
42  public class SubgraphQuery {
43  
44      /**
45       * Create a query that returns a subgraph at and below the node with the supplied path and the supplied UUID.
46       * 
47       * @param entities the entity manager; may not be null
48       * @param workspaceId the ID of the workspace; may not be null
49       * @param subgraphRootUuid the UUID (in string form) of the root node in the subgraph
50       * @param maxDepth the maximum depth of the subgraph, or 0 if there is no maximum depth
51       * @return the object representing the subgraph
52       */
53      public static SubgraphQuery create( EntityManager entities,
54                                          Long workspaceId,
55                                          UUID subgraphRootUuid,
56                                          int maxDepth ) {
57          assert entities != null;
58          assert subgraphRootUuid != null;
59          assert workspaceId != null;
60          assert maxDepth >= 0;
61          if (maxDepth == 0) maxDepth = Integer.MAX_VALUE;
62          final String subgraphRootUuidString = subgraphRootUuid.toString();
63          // Create a new subgraph query, and add a child for the root ...
64  
65          SubgraphQueryEntity query = new SubgraphQueryEntity(workspaceId, subgraphRootUuidString);
66          entities.persist(query);
67          Long queryId = query.getId();
68  
69          try {
70              // Insert a node for the root (this will be the starting point for the recursive operation) ...
71              SubgraphNodeEntity root = new SubgraphNodeEntity(queryId, subgraphRootUuidString, 0);
72              entities.persist(root);
73  
74              // Now add the children by inserting the children, one level at a time.
75              // Note that we do this for the root, and for each level until 1 BEYOND
76              // the max depth (so that we can get the children for the nodes that are
77              // at the maximum depth)...
78              Query statement = entities.createNamedQuery("SubgraphNodeEntity.insertChildren");
79              int numChildrenInserted = 0;
80              int parentLevel = 0;
81              while (parentLevel <= maxDepth) {
82                  // Insert the children of the next level by inserting via a select (join) of the children
83                  statement.setParameter("queryId", queryId);
84                  statement.setParameter("workspaceId", workspaceId);
85                  statement.setParameter("parentDepth", parentLevel);
86                  numChildrenInserted = statement.executeUpdate();
87                  if (numChildrenInserted == 0) break;
88                  parentLevel = parentLevel + 1;
89              }
90          } catch (RuntimeException t) {
91              // Clean up the search and results ...
92              try {
93                  Query search = entities.createNamedQuery("SubgraphNodeEntity.deleteByQueryId");
94                  search.setParameter("queryId", query.getId());
95                  search.executeUpdate();
96              } finally {
97                  entities.remove(query);
98              }
99              throw t;
100         }
101 
102         return new SubgraphQuery(entities, workspaceId, query, maxDepth);
103     }
104 
105     private final EntityManager manager;
106     private final Long workspaceId;
107     private SubgraphQueryEntity query;
108     private final int maxDepth;
109 
110     protected SubgraphQuery( EntityManager manager,
111                              Long workspaceId,
112                              SubgraphQueryEntity query,
113                              int maxDepth ) {
114         assert manager != null;
115         assert query != null;
116         assert workspaceId != null;
117 
118         this.manager = manager;
119         this.workspaceId = workspaceId;
120         this.query = query;
121         this.maxDepth = maxDepth;
122     }
123 
124     /**
125      * @return maxDepth
126      */
127     public int getMaxDepth() {
128         return maxDepth;
129     }
130 
131     /**
132      * @return manager
133      */
134     public EntityManager getEntityManager() {
135         return manager;
136     }
137 
138     /**
139      * Get the {@link NodeEntity root node} of the subgraph. This must be called before the query is {@link #close() closed}.
140      * 
141      * @return the subgraph's root nodes
142      */
143     public NodeEntity getNode() {
144         // Now query for all the nodes and put into a list ...
145         Query search = manager.createNamedQuery("SubgraphNodeEntity.getChildEntities");
146         search.setParameter("queryId", query.getId());
147         search.setParameter("workspaceId", workspaceId);
148         search.setParameter("depth", 0);
149         search.setParameter("maxDepth", 0);
150 
151         // Now process the nodes below the subgraph's root ...
152         return (NodeEntity)search.getSingleResult();
153     }
154 
155     /**
156      * Get the {@link NodeEntity nodes} in the subgraph. This must be called before the query is {@link #close() closed}.
157      * 
158      * @param includeRoot true if the subgraph's root node is to be included, or false otherwise
159      * @param includeChildrenOfMaxDepthNodes true if the method is to include nodes that are children of nodes that are at the
160      *        maximum depth, or false if only nodes up to the maximum depth are to be included
161      * @return the list of nodes, in breadth-first order
162      */
163     @SuppressWarnings( "unchecked" )
164     public List<NodeEntity> getNodes( boolean includeRoot,
165                                       boolean includeChildrenOfMaxDepthNodes ) {
166         if (query == null) throw new IllegalStateException();
167         // Now query for all the nodes and put into a list ...
168         Query search = manager.createNamedQuery("SubgraphNodeEntity.getChildEntities");
169         search.setParameter("queryId", query.getId());
170         search.setParameter("workspaceId", workspaceId);
171         search.setParameter("depth", includeRoot ? 0 : 1);
172         search.setParameter("maxDepth", includeChildrenOfMaxDepthNodes ? maxDepth : maxDepth - 1);
173 
174         // Now process the nodes below the subgraph's root ...
175         return search.getResultList();
176     }
177 
178     /**
179      * Delete the nodes in the subgraph.
180      * 
181      * @param includeRoot true if the root node should also be deleted
182      */
183     @SuppressWarnings( "unchecked" )
184     public void deleteSubgraph( boolean includeRoot ) {
185         if (query == null) throw new IllegalStateException();
186 
187         List<NodeEntity> nodes = getNodes(true, true);
188         List<String> uuids = new ArrayList<String>(nodes.size());
189         for (NodeEntity node : nodes) {
190             uuids.add(node.getNodeUuidString());
191         }
192 
193         // Delete the LargeValueEntities ...
194         Query withLargeValues = manager.createNamedQuery("SubgraphNodeEntity.getNodeEntitiesWithLargeValues");
195         withLargeValues.setParameter("queryId", query.getId());
196         withLargeValues.setParameter("depth", includeRoot ? 0 : 1);
197         withLargeValues.setParameter("workspaceId", workspaceId);
198         List<NodeEntity> nodesWithLargeValues = withLargeValues.getResultList();
199         if (nodesWithLargeValues.size() != 0) {
200             for (NodeEntity node : nodesWithLargeValues) {
201                 node.getLargeValues().clear();
202             }
203             manager.flush();
204         }
205 
206         // Delete the ChildEntities ...
207         Query delete = manager.createNamedQuery("SubgraphNodeEntity.clearParentReferences");
208         delete.setParameter("queryId", query.getId());
209         delete.setParameter("depth", includeRoot ? 0 : 1);
210         delete.setParameter("workspaceId", workspaceId);
211         delete.executeUpdate();
212 
213         delete = manager.createNamedQuery("SubgraphNodeEntity.deleteChildEntities");
214         delete.setParameter("queryId", query.getId());
215         delete.setParameter("depth", includeRoot ? 0 : 1);
216         delete.setParameter("workspaceId", workspaceId);
217         delete.executeUpdate();
218 
219         manager.flush();
220     }
221 
222     /**
223      * Close this query object and clean up all in-database records associated with this query. This method <i>must</i> be called
224      * when this query is no longer needed, and once it is called, this subgraph query is no longer usable.
225      */
226     public void close() {
227         if (query == null) return;
228         // Clean up the search and results ...
229         try {
230             Query search = manager.createNamedQuery("SubgraphNodeEntity.deleteByQueryId");
231             search.setParameter("queryId", query.getId());
232             search.executeUpdate();
233         } finally {
234             try {
235                 manager.remove(query);
236             } finally {
237                 query = null;
238             }
239         }
240     }
241 }