001    /*
002     * JBoss DNA (http://www.jboss.org/dna)
003     * See the COPYRIGHT.txt file distributed with this work for information
004     * regarding copyright ownership.  Some portions may be licensed
005     * to Red Hat, Inc. under one or more contributor license agreements.
006     * See the AUTHORS.txt file in the distribution for a full listing of 
007     * individual contributors. 
008     *
009     * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010     * is licensed to you under the terms of the GNU Lesser General Public License as
011     * published by the Free Software Foundation; either version 2.1 of
012     * the License, or (at your option) any later version.
013     *
014     * JBoss DNA is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     * Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this software; if not, write to the Free
021     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023     */
024    package org.jboss.dna.connector.store.jpa.model.basic;
025    
026    import java.util.List;
027    import javax.persistence.Column;
028    import javax.persistence.Entity;
029    import javax.persistence.EntityManager;
030    import javax.persistence.Id;
031    import javax.persistence.JoinColumn;
032    import javax.persistence.ManyToOne;
033    import javax.persistence.NamedQueries;
034    import javax.persistence.NamedQuery;
035    import javax.persistence.Query;
036    import javax.persistence.Table;
037    import org.hibernate.annotations.Index;
038    import org.jboss.dna.common.text.Inflector;
039    import org.jboss.dna.common.util.HashCode;
040    import org.jboss.dna.connector.store.jpa.model.common.NamespaceEntity;
041    
042    /**
043     * An entity representing the parent-child relationship between two nodes. In addition to the references to the parent and child
044     * nodes, this entity also maintains the indexInParent of the indexInParent within the parent node's list of all children, the
045     * child's name ( {@link #getChildName() local part} and {@link #getChildNamespace() namespace}), and the same-name-sibiling
046     * indexInParent (if there is one).
047     * 
048     * @author Randall Hauch
049     */
050    @Entity
051    @Table( name = "DNA_BASIC_CHILDREN" )
052    @org.hibernate.annotations.Table( appliesTo = "DNA_BASIC_CHILDREN", indexes = {
053        @Index( name = "CHILDINDEX_INX", columnNames = {"WORKSPACE_ID", "PARENT_UUID", "CHILD_INDEX"} ),
054        @Index( name = "CHILDUUID_INX", columnNames = {"WORKSPACE_ID", "CHILD_UUID"} ),
055        @Index( name = "CHILDNAME_INX", columnNames = {"WORKSPACE_ID", "PARENT_UUID", "CHILD_NAME_NS_ID", "CHILD_NAME_LOCAL",
056            "SNS_INDEX"} )} )
057    @NamedQueries( {
058        @NamedQuery( name = "ChildEntity.findByPathSegment", query = "select child from ChildEntity as child where child.id.workspaceId = :workspaceId and child.id.parentUuidString = :parentUuidString AND child.childNamespace.id = :ns AND child.childName = :childName AND child.sameNameSiblingIndex = :sns" ),
059        @NamedQuery( name = "ChildEntity.findAllUnderParent", query = "select child from ChildEntity as child where child.id.workspaceId = :workspaceId and child.id.parentUuidString = :parentUuidString order by child.indexInParent" ),
060        @NamedQuery( name = "ChildEntity.findRangeUnderParent", query = "select child from ChildEntity as child where child.id.workspaceId = :workspaceId and child.id.parentUuidString = :parentUuidString and child.indexInParent >= :firstIndex and child.indexInParent < :afterIndex order by child.indexInParent" ),
061        @NamedQuery( name = "ChildEntity.findChildrenAfterIndexUnderParent", query = "select child from ChildEntity as child where child.id.workspaceId = :workspaceId and child.id.parentUuidString = :parentUuidString and child.indexInParent >= :afterIndex order by child.indexInParent" ),
062        @NamedQuery( name = "ChildEntity.findByChildUuid", query = "select child from ChildEntity as child where child.id.workspaceId = :workspaceId and child.id.childUuidString = :childUuidString" ),
063        @NamedQuery( name = "ChildEntity.findMaximumSnsIndex", query = "select max(child.sameNameSiblingIndex) from ChildEntity as child where child.id.workspaceId = :workspaceId and child.id.parentUuidString = :parentUuid AND child.childNamespace.id = :ns AND child.childName = :childName" ),
064        @NamedQuery( name = "ChildEntity.findMaximumChildIndex", query = "select max(child.indexInParent) from ChildEntity as child where child.id.workspaceId = :workspaceId and child.id.parentUuidString = :parentUuid" ),
065        @NamedQuery( name = "ChildEntity.findInWorkspace", query = "select child from ChildEntity as child where child.id.workspaceId = :workspaceId" )} )
066    public class ChildEntity {
067    
068        @Id
069        private ChildId id;
070    
071        /** The zero-based index */
072        @Column( name = "CHILD_INDEX", nullable = false, unique = false )
073        private int indexInParent;
074    
075        @ManyToOne
076        @JoinColumn( name = "CHILD_NAME_NS_ID", nullable = false )
077        private NamespaceEntity childNamespace;
078    
079        @Column( name = "CHILD_NAME_LOCAL", nullable = false, unique = false, length = 512 )
080        private String childName;
081    
082        @Column( name = "SNS_INDEX", nullable = false, unique = false )
083        private int sameNameSiblingIndex;
084    
085        /**
086         * Tracks whether this node allows or disallows its children to have the same names (to be same-name-siblings). The model uses
087         * this to know whether it can optimization the database operations when creating, inserting, or removing children.
088         */
089        @Column( name = "ALLOWS_SNS", nullable = false, unique = false )
090        private boolean allowsSameNameChildren;
091    
092        public ChildEntity() {
093        }
094    
095        public ChildEntity( ChildId id,
096                            int indexInParent,
097                            NamespaceEntity ns,
098                            String name ) {
099            this.id = id;
100            this.indexInParent = indexInParent;
101            this.childNamespace = ns;
102            this.childName = name;
103            this.sameNameSiblingIndex = 1;
104        }
105    
106        public ChildEntity( ChildId id,
107                            int indexInParent,
108                            NamespaceEntity ns,
109                            String name,
110                            int sameNameSiblingIndex ) {
111            this.id = id;
112            this.indexInParent = indexInParent;
113            this.childNamespace = ns;
114            this.childName = name;
115            this.sameNameSiblingIndex = sameNameSiblingIndex;
116        }
117    
118        /**
119         * @return parent
120         */
121        public ChildId getId() {
122            return id;
123        }
124    
125        /**
126         * @param childId Sets parent to the specified value.
127         */
128        public void setId( ChildId childId ) {
129            this.id = childId;
130        }
131    
132        /**
133         * Get the zero-based index of this child within the parent's list of children
134         * 
135         * @return the zero-based index of this child
136         */
137        public int getIndexInParent() {
138            return indexInParent;
139        }
140    
141        /**
142         * @param index Sets indexInParent to the specified value.
143         */
144        public void setIndexInParent( int index ) {
145            this.indexInParent = index;
146        }
147    
148        /**
149         * @return childName
150         */
151        public String getChildName() {
152            return childName;
153        }
154    
155        /**
156         * @param childName Sets childName to the specified value.
157         */
158        public void setChildName( String childName ) {
159            this.childName = childName;
160        }
161    
162        /**
163         * @return childNamespace
164         */
165        public NamespaceEntity getChildNamespace() {
166            return childNamespace;
167        }
168    
169        /**
170         * @param childNamespace Sets childNamespace to the specified value.
171         */
172        public void setChildNamespace( NamespaceEntity childNamespace ) {
173            this.childNamespace = childNamespace;
174        }
175    
176        /**
177         * @return sameNameSiblingIndex
178         */
179        public int getSameNameSiblingIndex() {
180            return sameNameSiblingIndex;
181        }
182    
183        /**
184         * @param sameNameSiblingIndex Sets sameNameSiblingIndex to the specified value.
185         */
186        public void setSameNameSiblingIndex( int sameNameSiblingIndex ) {
187            this.sameNameSiblingIndex = sameNameSiblingIndex;
188        }
189    
190        /**
191         * @return allowsSameNameChildren
192         */
193        public boolean getAllowsSameNameChildren() {
194            return allowsSameNameChildren;
195        }
196    
197        /**
198         * @param allowsSameNameChildren Sets allowsSameNameChildren to the specified value.
199         */
200        public void setAllowsSameNameChildren( boolean allowsSameNameChildren ) {
201            this.allowsSameNameChildren = allowsSameNameChildren;
202        }
203    
204        /**
205         * {@inheritDoc}
206         * 
207         * @see java.lang.Object#hashCode()
208         */
209        @Override
210        public int hashCode() {
211            return HashCode.compute(id);
212        }
213    
214        /**
215         * {@inheritDoc}
216         * 
217         * @see java.lang.Object#equals(java.lang.Object)
218         */
219        @Override
220        public boolean equals( Object obj ) {
221            if (obj == this) return true;
222            if (obj instanceof ChildEntity) {
223                ChildEntity that = (ChildEntity)obj;
224                if (this.id == null) {
225                    if (that.id != null) return false;
226                } else {
227                    if (!this.id.equals(that.id)) return false;
228                }
229                return true;
230            }
231            return false;
232        }
233    
234        /**
235         * {@inheritDoc}
236         * 
237         * @see java.lang.Object#toString()
238         */
239        @Override
240        public String toString() {
241            StringBuilder sb = new StringBuilder();
242            if (childNamespace != null) {
243                sb.append('{').append(childNamespace).append("}:");
244            }
245            sb.append(childName);
246            if (sameNameSiblingIndex > 1) {
247                sb.append('[').append(sameNameSiblingIndex).append(']');
248            }
249            if (id != null) {
250                sb.append(" (id=").append(id.getChildUuidString()).append(")");
251                String parentId = id.getParentUuidString();
252                if (parentId != null) {
253                    sb.append(" is ");
254                    sb.append(Inflector.getInstance().ordinalize(indexInParent));
255                    sb.append(" child of ");
256                    sb.append(parentId);
257                    sb.append(" in workspace ");
258                    sb.append(id.getWorkspaceId());
259                } else {
260                    sb.append(" is root in workspace ");
261                    sb.append(id.getWorkspaceId());
262                }
263            }
264            return sb.toString();
265        }
266    
267        @SuppressWarnings( "unchecked" )
268        public static void adjustSnsIndexesAndIndexesAfterRemoving( EntityManager entities,
269                                                                    Long workspaceId,
270                                                                    String uuidParent,
271                                                                    String childName,
272                                                                    long childNamespaceIndex,
273                                                                    int childIndex ) {
274            // Decrement the 'indexInParent' index values for all nodes above the previously removed sibling ...
275            Query query = entities.createNamedQuery("ChildEntity.findChildrenAfterIndexUnderParent");
276            query.setParameter("workspaceId", workspaceId);
277            query.setParameter("parentUuidString", uuidParent);
278            query.setParameter("afterIndex", childIndex);
279            for (ChildEntity entity : (List<ChildEntity>)query.getResultList()) {
280                // Decrement the index in parent ...
281                entity.setIndexInParent(entity.getIndexInParent() - 1);
282                if (entity.getChildName().equals(childName) && entity.getChildNamespace().getId() == childNamespaceIndex) {
283                    // The name matches, so decrement the SNS index ...
284                    entity.setSameNameSiblingIndex(entity.getSameNameSiblingIndex() - 1);
285                }
286            }
287        }
288    
289    }