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.Collection;
28  import java.util.HashSet;
29  import java.util.List;
30  import javax.persistence.Column;
31  import javax.persistence.Entity;
32  import javax.persistence.EntityManager;
33  import javax.persistence.FetchType;
34  import javax.persistence.GeneratedValue;
35  import javax.persistence.GenerationType;
36  import javax.persistence.Id;
37  import javax.persistence.JoinColumn;
38  import javax.persistence.JoinTable;
39  import javax.persistence.Lob;
40  import javax.persistence.ManyToMany;
41  import javax.persistence.ManyToOne;
42  import javax.persistence.NamedQueries;
43  import javax.persistence.NamedQuery;
44  import javax.persistence.OneToMany;
45  import javax.persistence.OrderBy;
46  import javax.persistence.Query;
47  import javax.persistence.Table;
48  import org.hibernate.annotations.Index;
49  import org.modeshape.common.text.Inflector;
50  import org.modeshape.common.util.HashCode;
51  import org.modeshape.connector.store.jpa.model.common.NamespaceEntity;
52  import org.modeshape.connector.store.jpa.util.Serializer;
53  
54  /**
55   * An entity a node and its properties. In addition to the references to the parent and child nodes, this entity also maintains
56   * the indexInParent of the indexInParent within the parent node's list of all children, the child's name (
57   * {@link #getChildName() local part} and {@link #getChildNamespace() namespace}), and the same-name-sibling indexInParent (if
58   * there is one).
59   */
60  @Entity
61  @org.hibernate.annotations.Table( appliesTo = "MODE_SIMPLE_NODE", indexes = {
62      @Index( name = "NODEUUID_INX", columnNames = {"WORKSPACE_ID", "NODE_UUID"} ),
63      @Index( name = "CHILDINDEX_INX", columnNames = {"WORKSPACE_ID", "PARENT_ID", "CHILD_INDEX"} ),
64      @Index( name = "CHILDNAME_INX", columnNames = {"WORKSPACE_ID", "PARENT_ID", "CHILD_NAME_NS_ID", "CHILD_NAME_LOCAL",
65          "SNS_INDEX"} )} )
66  @Table( name = "MODE_SIMPLE_NODE" )
67  @NamedQueries( {
68      @NamedQuery( name = "NodeEntity.findByNodeUuid", query = "from NodeEntity as node where node.workspaceId = :workspaceId and node.nodeUuidString = :nodeUuidString" ),
69      @NamedQuery( name = "NodeEntity.findInWorkspace", query = "from NodeEntity as node where node.workspaceId = :workspaceId" ),
70      @NamedQuery( name = "NodeEntity.deleteAllInWorkspace", query = "delete from NodeEntity where workspaceId = :workspaceId" ),
71      @NamedQuery( name = "NodeEntity.withLargeValues", query = "from NodeEntity as node where node.workspaceId = :workspaceId and size(node.largeValues) > 0" )} )
72  public class NodeEntity {
73  
74      @Id
75      @GeneratedValue( strategy = GenerationType.AUTO )
76      @Column( name = "ID" )
77      private long id;
78  
79      @Column( name = "WORKSPACE_ID", nullable = false )
80      private long workspaceId;
81  
82      @ManyToOne( fetch = FetchType.LAZY )
83      @JoinColumn( name = "PARENT_ID", referencedColumnName = "id", nullable = true )
84      private NodeEntity parent;
85  
86      @Column( name = "NODE_UUID", nullable = false, length = 36 )
87      private String nodeUuidString;
88  
89      /** The zero-based index */
90      @Column( name = "CHILD_INDEX", nullable = false, unique = false )
91      private int indexInParent = 0;
92  
93      @ManyToOne( fetch = FetchType.LAZY )
94      @JoinColumn( name = "CHILD_NAME_NS_ID", nullable = true )
95      private NamespaceEntity childNamespace;
96  
97      @Column( name = "CHILD_NAME_LOCAL", nullable = true, unique = false, length = 512 )
98      private String childName;
99  
100     @Column( name = "SNS_INDEX", nullable = false, unique = false )
101     private int sameNameSiblingIndex = 1;
102 
103     @OneToMany( fetch = FetchType.LAZY, mappedBy = "parent" )
104     @OrderBy( "indexInParent" )
105     private final List<NodeEntity> children = new ArrayList<NodeEntity>();
106     /**
107      * Tracks whether this node allows or disallows its children to have the same names (to be same-name-siblings). The model uses
108      * this to know whether it can optimization the database operations when creating, inserting, or removing children.
109      */
110     @Column( name = "ALLOWS_SNS", nullable = false, unique = false )
111     private boolean allowsSameNameChildren;
112 
113     @Lob
114     @Column( name = "DATA", nullable = true, unique = false )
115     private byte[] data;
116 
117     @Column( name = "NUM_PROPS", nullable = false )
118     private int propertyCount;
119 
120     /**
121      * Flag specifying whether the binary data is stored in a compressed format.
122      */
123     @Column( name = "COMPRESSED", nullable = true )
124     private Boolean compressed;
125 
126     /**
127      * Flag specifying whether this node should be included in referential integrity enforcement.
128      */
129     @Column( name = "ENFORCEREFINTEG", nullable = false )
130     private boolean referentialIntegrityEnforced = true;
131 
132     // @org.hibernate.annotations.CollectionOfElements( fetch = FetchType.LAZY )
133     // @JoinTable( name = "ModeShape_LARGEVALUE_USAGES", joinColumns = {@JoinColumn( name = "WORKSPACE_ID" ),
134     // @JoinColumn( name = "NODE_UUID" )} )
135     @ManyToMany
136     @JoinTable( name = "ModeShape_LARGEVALUE_USAGES", joinColumns = {@JoinColumn( name = "ID" )} )
137     private final Collection<LargeValueEntity> largeValues = new HashSet<LargeValueEntity>();
138 
139     public NodeEntity() {
140     }
141 
142     public NodeEntity( long id,
143                        NodeEntity parent,
144                        String nodeUuidString,
145                        long workspaceId,
146                        int indexInParent,
147                        NamespaceEntity ns,
148                        String name ) {
149         this.id = id;
150         this.parent = parent;
151         this.nodeUuidString = nodeUuidString;
152         this.workspaceId = workspaceId;
153         this.indexInParent = indexInParent;
154         this.childNamespace = ns;
155         this.childName = name;
156         this.sameNameSiblingIndex = 1;
157     }
158 
159     public NodeEntity( long id,
160                        NodeEntity parent,
161                        String nodeUuidString,
162                        long workspaceId,
163                        int indexInParent,
164                        NamespaceEntity ns,
165                        String name,
166                        int sameNameSiblingIndex ) {
167         this.id = id;
168         this.parent = parent;
169         this.nodeUuidString = nodeUuidString;
170         this.workspaceId = workspaceId;
171         this.indexInParent = indexInParent;
172         this.childNamespace = ns;
173         this.childName = name;
174         this.sameNameSiblingIndex = sameNameSiblingIndex;
175     }
176 
177     /**
178      * Returns this node's unique identifier
179      * 
180      * @return this node's unique identifier
181      */
182     public long getNodeId() {
183         return id;
184     }
185 
186     /**
187      * @param id Sets this node's unique identifier
188      */
189     public void setNodeId( long id ) {
190         this.id = id;
191     }
192 
193     /**
194      * Returns the parent identifier
195      * 
196      * @return the parent identifier
197      */
198     public NodeEntity getParent() {
199         return parent;
200     }
201 
202     /**
203      * Sets the parent identifier
204      * 
205      * @param parent the parent identifier
206      */
207     public void setParent( NodeEntity parent ) {
208         this.parent = parent;
209     }
210 
211     /**
212      * Returns the node UUID string
213      * 
214      * @return the node UUID string
215      */
216     public String getNodeUuidString() {
217         return nodeUuidString;
218     }
219 
220     /**
221      * Sets the node UUID string
222      * 
223      * @param nodeUuidString the node UUID string
224      */
225     public void setNodeUuidString( String nodeUuidString ) {
226         this.nodeUuidString = nodeUuidString;
227     }
228 
229     /**
230      * Returns the identifier of the workspace containing this node
231      * 
232      * @return the identifier of the workspace containing this node
233      */
234     public long getWorkspaceId() {
235         return workspaceId;
236     }
237 
238     /**
239      * Sets the identifier of the workspace containing this node
240      * 
241      * @param workspaceId the identifier of the workspace containing this node
242      */
243     public void setWorkspaceId( long workspaceId ) {
244         this.workspaceId = workspaceId;
245     }
246 
247     /**
248      * Get the zero-based index of this child within the parent's list of children
249      * 
250      * @return the zero-based index of this child
251      */
252     public int getIndexInParent() {
253         return indexInParent;
254     }
255 
256     /**
257      * @param index Sets indexInParent to the specified value.
258      */
259     public void setIndexInParent( int index ) {
260         this.indexInParent = index;
261     }
262 
263     /**
264      * @return childName
265      */
266     public String getChildName() {
267         return childName;
268     }
269 
270     /**
271      * @param childName Sets childName to the specified value.
272      */
273     public void setChildName( String childName ) {
274         this.childName = childName;
275     }
276 
277     /**
278      * @return childNamespace
279      */
280     public NamespaceEntity getChildNamespace() {
281         return childNamespace;
282     }
283 
284     /**
285      * @param childNamespace Sets childNamespace to the specified value.
286      */
287     public void setChildNamespace( NamespaceEntity childNamespace ) {
288         this.childNamespace = childNamespace;
289     }
290 
291     /**
292      * @return sameNameSiblingIndex
293      */
294     public int getSameNameSiblingIndex() {
295         return sameNameSiblingIndex;
296     }
297 
298     /**
299      * @param sameNameSiblingIndex Sets sameNameSiblingIndex to the specified value.
300      */
301     public void setSameNameSiblingIndex( int sameNameSiblingIndex ) {
302         this.sameNameSiblingIndex = sameNameSiblingIndex;
303     }
304 
305     /**
306      * @return allowsSameNameChildren
307      */
308     public boolean getAllowsSameNameChildren() {
309         return allowsSameNameChildren;
310     }
311 
312     /**
313      * @param allowsSameNameChildren Sets allowsSameNameChildren to the specified value.
314      */
315     public void setAllowsSameNameChildren( boolean allowsSameNameChildren ) {
316         this.allowsSameNameChildren = allowsSameNameChildren;
317     }
318 
319     /**
320      * Get the data that represents the {@link Serializer packed} properties.
321      * 
322      * @return the raw data representing the properties
323      */
324     public byte[] getData() {
325         return data;
326     }
327 
328     /**
329      * Set the data that represents the {@link Serializer packed} properties.
330      * 
331      * @param data the raw data representing the properties
332      */
333     public void setData( byte[] data ) {
334         this.data = data;
335     }
336 
337     /**
338      * @return propertyCount
339      */
340     public int getPropertyCount() {
341         return propertyCount;
342     }
343 
344     /**
345      * @param propertyCount Sets propertyCount to the specified value.
346      */
347     public void setPropertyCount( int propertyCount ) {
348         this.propertyCount = propertyCount;
349     }
350 
351     /**
352      * @return compressed
353      */
354     public boolean isCompressed() {
355         return compressed != null && compressed.booleanValue();
356     }
357 
358     /**
359      * @param compressed Sets compressed to the specified value.
360      */
361     public void setCompressed( boolean compressed ) {
362         this.compressed = Boolean.valueOf(compressed);
363     }
364 
365     public List<NodeEntity> getChildren() {
366         return children;
367     }
368 
369     public void addChild( NodeEntity child ) {
370         children.add(child);
371         child.setIndexInParent(children.size() - 1);
372     }
373 
374     public void addChild( int index,
375                           NodeEntity child ) {
376         for (NodeEntity existing : children.subList(index, children.size() - 1)) {
377             existing.setIndexInParent(existing.getIndexInParent() + 1);
378         }
379 
380         children.add(index, child);
381         child.setIndexInParent(index);
382     }
383 
384     public boolean removeChild( int index ) {
385         NodeEntity removedNode = children.remove(index);
386         if (removedNode == null) return false;
387         removedNode.setParent(null);
388 
389         if (index < children.size()) {
390             for (NodeEntity child : children.subList(index, children.size() - 1)) {
391                 child.setIndexInParent(child.getIndexInParent() - 1);
392             }
393         }
394         return true;
395     }
396 
397     /**
398      * @return largeValues
399      */
400     public Collection<LargeValueEntity> getLargeValues() {
401         return largeValues;
402     }
403 
404     /**
405      * @return referentialIntegrityEnforced
406      */
407     public boolean isReferentialIntegrityEnforced() {
408         return referentialIntegrityEnforced;
409     }
410 
411     /**
412      * @param referentialIntegrityEnforced Sets referentialIntegrityEnforced to the specified value.
413      */
414     public void setReferentialIntegrityEnforced( boolean referentialIntegrityEnforced ) {
415         this.referentialIntegrityEnforced = referentialIntegrityEnforced;
416     }
417 
418     /**
419      * {@inheritDoc}
420      * 
421      * @see java.lang.Object#hashCode()
422      */
423     @Override
424     public int hashCode() {
425         return HashCode.compute(id);
426     }
427 
428     /**
429      * {@inheritDoc}
430      * 
431      * @see java.lang.Object#equals(java.lang.Object)
432      */
433     @Override
434     public boolean equals( Object obj ) {
435         if (obj == this) return true;
436         if (obj instanceof NodeEntity) {
437             NodeEntity that = (NodeEntity)obj;
438             if (this.id != that.id) return false;
439             return true;
440         }
441         return false;
442     }
443 
444     /**
445      * {@inheritDoc}
446      * 
447      * @see java.lang.Object#toString()
448      */
449     @Override
450     public String toString() {
451         StringBuilder sb = new StringBuilder();
452         if (childNamespace != null) {
453             sb.append('{').append(childNamespace).append("}:");
454         }
455         sb.append(childName);
456         if (sameNameSiblingIndex > 1) {
457             sb.append('[').append(sameNameSiblingIndex).append(']');
458         }
459         sb.append(" (id=").append(getNodeUuidString()).append(")");
460         if (parent != null) {
461             sb.append(" is ");
462             sb.append(Inflector.getInstance().ordinalize(indexInParent));
463             sb.append(" child of ");
464             sb.append(parent.getNodeId());
465             sb.append(" in workspace ");
466             sb.append(getWorkspaceId());
467         } else {
468             sb.append(" is root in workspace ");
469             sb.append(getWorkspaceId());
470         }
471         return sb.toString();
472     }
473 
474     @SuppressWarnings( "unchecked" )
475     public static void adjustSnsIndexesAndIndexesAfterRemoving( EntityManager entities,
476                                                                 Long workspaceId,
477                                                                 String uuidParent,
478                                                                 String childName,
479                                                                 long childNamespaceIndex,
480                                                                 int childIndex ) {
481         // Decrement the 'indexInParent' index values for all nodes above the previously removed sibling ...
482         Query query = entities.createNamedQuery("NodeEntity.findChildrenAfterIndexUnderParent");
483         query.setParameter("workspaceId", workspaceId);
484         query.setParameter("parentUuidString", uuidParent);
485         query.setParameter("afterIndex", childIndex);
486         for (NodeEntity entity : (List<NodeEntity>)query.getResultList()) {
487             // Decrement the index in parent ...
488             entity.setIndexInParent(entity.getIndexInParent() - 1);
489             if (entity.getChildName().equals(childName) && entity.getChildNamespace().getId() == childNamespaceIndex) {
490                 // The name matches, so decrement the SNS index ...
491                 entity.setSameNameSiblingIndex(entity.getSameNameSiblingIndex() - 1);
492             }
493         }
494     }
495 
496     @SuppressWarnings( "unchecked" )
497     public static int adjustSnsIndexesAndIndexes( EntityManager entities,
498                                                   Long workspaceId,
499                                                   String uuidParent,
500                                                   int afterIndex,
501                                                   int untilIndex,
502                                                   long childNamespaceIndex,
503                                                   String childName,
504                                                   int modifier ) {
505         int snsCount = 0;
506 
507         // Decrement the 'indexInParent' index values for all nodes above the previously removed sibling ...
508         Query query = entities.createNamedQuery("NodeEntity.findChildrenAfterIndexUnderParent");
509         query.setParameter("workspaceId", workspaceId);
510         query.setParameter("parentUuidString", uuidParent);
511         query.setParameter("afterIndex", afterIndex);
512 
513         int index = afterIndex;
514         for (NodeEntity entity : (List<NodeEntity>)query.getResultList()) {
515             if (++index > untilIndex) {
516                 break;
517             }
518 
519             // Decrement the index in parent ...
520             entity.setIndexInParent(entity.getIndexInParent() + modifier);
521             if (entity.getChildName().equals(childName) && entity.getChildNamespace().getId() == childNamespaceIndex) {
522                 // The name matches, so decrement the SNS index ...
523                 entity.setSameNameSiblingIndex(entity.getSameNameSiblingIndex() + modifier);
524                 snsCount++;
525             }
526         }
527 
528         return snsCount;
529     }
530 }