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.jcr;
25  
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.Map;
29  import java.util.Set;
30  import javax.jcr.nodetype.NodeDefinition;
31  import javax.jcr.nodetype.NodeType;
32  import net.jcip.annotations.Immutable;
33  import org.modeshape.graph.ExecutionContext;
34  import org.modeshape.graph.property.Name;
35  import org.modeshape.graph.property.ValueFactory;
36  
37  /**
38   * ModeShape implementation of the {@link NodeDefinition} class.
39   */
40  @Immutable
41  class JcrNodeDefinition extends JcrItemDefinition implements NodeDefinition {
42  
43      /** @see NodeDefinition#allowsSameNameSiblings() */
44      private final boolean allowsSameNameSiblings;
45  
46      /**
47       * The name of the default primary type (if any). The name is used instead of the raw node type to allow circular references a
48       * la <code>nt:unstructured</code>.
49       */
50      private final Name defaultPrimaryTypeName;
51  
52      /** @see NodeDefinition#getRequiredPrimaryTypes() */
53      private Map<Name, JcrNodeType> requiredPrimaryTypesByName;
54  
55      private JcrNodeType[] requiredPrimaryTypes;
56  
57      private Name[] requiredPrimaryTypeNames;
58  
59      /** A durable identifier for this node definition. */
60      private NodeDefinitionId id;
61  
62      /** Link to the repository node type manager */
63      private final RepositoryNodeTypeManager nodeTypeManager;
64  
65      JcrNodeDefinition( ExecutionContext context,
66                         JcrNodeType declaringNodeType,
67                         Name name,
68                         int onParentVersion,
69                         boolean autoCreated,
70                         boolean mandatory,
71                         boolean protectedItem,
72                         boolean allowsSameNameSiblings,
73                         Name defaultPrimaryTypeName,
74                         Name[] requiredPrimaryTypeNames ) {
75          this(context, null, declaringNodeType, name, onParentVersion, autoCreated, mandatory, protectedItem,
76               allowsSameNameSiblings, defaultPrimaryTypeName, requiredPrimaryTypeNames);
77      }
78  
79      JcrNodeDefinition( ExecutionContext context,
80                         RepositoryNodeTypeManager nodeTypeManager,
81                         JcrNodeType declaringNodeType,
82                         Name name,
83                         int onParentVersion,
84                         boolean autoCreated,
85                         boolean mandatory,
86                         boolean protectedItem,
87                         boolean allowsSameNameSiblings,
88                         Name defaultPrimaryTypeName,
89                         Name[] requiredPrimaryTypeNames ) {
90          super(context, declaringNodeType, name, onParentVersion, autoCreated, mandatory, protectedItem);
91          this.nodeTypeManager = nodeTypeManager;
92          this.allowsSameNameSiblings = allowsSameNameSiblings;
93          this.defaultPrimaryTypeName = defaultPrimaryTypeName;
94          this.requiredPrimaryTypes = new JcrNodeType[requiredPrimaryTypeNames.length];
95          this.requiredPrimaryTypeNames = requiredPrimaryTypeNames;
96      }
97  
98      private final String string( Name name ) {
99          if (name == null) return null;
100         if (this.context == null) return name.getString();
101 
102         return name.getString(context.getNamespaceRegistry());
103     }
104 
105     /**
106      * Checks that the fields derived from requiredPrimaryTypeNames are initialized.
107      * <p>
108      * This was pulled out of the constructor to make type registration more flexible by deferring node type lookup for required
109      * primary types until after type registration is complete. This allows, for example, nodes to have themselves as required
110      * primary types of their children.
111      * </p>
112      */
113     private void ensureRequiredPrimaryTypesLoaded() {
114         if (requiredPrimaryTypesByName != null) return;
115         this.requiredPrimaryTypes = new JcrNodeType[requiredPrimaryTypeNames.length];
116         for (int i = 0; i != requiredPrimaryTypeNames.length; ++i) {
117             this.requiredPrimaryTypes[i] = nodeTypeManager.getNodeType(requiredPrimaryTypeNames[i]);
118         }
119         Map<Name, JcrNodeType> requiredPrimaryTypesByName = new HashMap<Name, JcrNodeType>();
120         for (JcrNodeType requiredPrimaryType : requiredPrimaryTypes) {
121             requiredPrimaryTypesByName.put(requiredPrimaryType.getInternalName(), requiredPrimaryType);
122         }
123         this.requiredPrimaryTypesByName = Collections.unmodifiableMap(requiredPrimaryTypesByName);
124 
125     }
126 
127     /**
128      * Get the durable identifier for this node definition.
129      * 
130      * @return the node definition ID; never null
131      */
132     public NodeDefinitionId getId() {
133         if (id == null) {
134             // This is idempotent, so no need to lock
135             id = new NodeDefinitionId(this.declaringNodeType.getInternalName(), this.name, this.requiredPrimaryTypeNames);
136         }
137         return id;
138     }
139 
140     /**
141      * {@inheritDoc}
142      * 
143      * @see javax.jcr.nodetype.NodeDefinition#allowsSameNameSiblings()
144      */
145     public boolean allowsSameNameSiblings() {
146         return allowsSameNameSiblings;
147     }
148 
149     /**
150      * @return the name of the default primary type for this definition; may be null
151      */
152     final Name defaultPrimaryTypeName() {
153         return defaultPrimaryTypeName;
154     }
155 
156     /**
157      * {@inheritDoc}
158      * 
159      * @see javax.jcr.nodetype.NodeDefinition#getDefaultPrimaryType()
160      */
161     public NodeType getDefaultPrimaryType() {
162         // It is valid for this field to be null.
163         if (defaultPrimaryTypeName == null) {
164             return null;
165         }
166 
167         return nodeTypeManager.getNodeType(defaultPrimaryTypeName);
168     }
169 
170     /**
171      * {@inheritDoc}
172      * 
173      * @see javax.jcr.nodetype.NodeDefinition#getRequiredPrimaryTypes()
174      */
175     public NodeType[] getRequiredPrimaryTypes() {
176         ensureRequiredPrimaryTypesLoaded();
177         if (requiredPrimaryTypes.length == 0) {
178             // Per the JavaDoc, this method should never return null or an empty array; if there are no constraints,
179             // then this method should include an array with 'nt:base' as the required primary type.
180             NodeType[] result = new NodeType[1];
181             result[0] = nodeTypeManager.getNodeType(JcrNtLexicon.BASE);
182             return result;
183         }
184         // Make a copy so that the caller can't modify our content ...
185         NodeType[] result = new NodeType[requiredPrimaryTypes.length];
186         for (int i = 0; i != requiredPrimaryTypes.length; ++i) {
187             result[i] = requiredPrimaryTypes[i];
188         }
189         return result;
190     }
191 
192     /**
193      * Returns the required primary type names for this object as specified in the constructor. This method is useful for callers
194      * that wish to access this information while this node definition's parent node is being registered.
195      * 
196      * @return the required primary type names
197      */
198     Name[] requiredPrimaryTypeNames() {
199         return this.requiredPrimaryTypeNames;
200     }
201 
202     /**
203      * Get the set of names of the primary types.
204      * 
205      * @return the required primary type names
206      */
207     Set<Name> requiredPrimaryTypeNameSet() {
208         ensureRequiredPrimaryTypesLoaded();
209         return requiredPrimaryTypesByName.keySet();
210     }
211 
212     /**
213      * @return the names of the required primary types for this child node definition; never null
214      */
215     public String[] getRequiredPrimaryTypeNames() {
216         if (requiredPrimaryTypeNames == null) return new String[0];
217 
218         String[] rptNames = new String[requiredPrimaryTypeNames.length];
219         for (int i = 0; i < requiredPrimaryTypeNames.length; i++) {
220             rptNames[i] = string(requiredPrimaryTypeNames[i]);
221         }
222         return rptNames;
223     }
224 
225     /**
226      * @return the name of the default primary type for this child node definition; may be null
227      */
228     public String getDefaultPrimaryTypeName() {
229         return string(this.defaultPrimaryTypeName);
230     }
231 
232     /**
233      * Determine if this node definition will allow a child with the supplied primary type. This method checks this definition's
234      * {@link #getRequiredPrimaryTypes()} against the supplied primary type and its supertypes. The supplied primary type for the
235      * child must be or extend all of the types defined by the {@link #getRequiredPrimaryTypes() required primary types}.
236      * 
237      * @param childPrimaryType the primary type of the child
238      * @return true if the primary type of the child (or one of its supertypes) is one of the types required by this definition,
239      *         or false otherwise
240      */
241     final boolean allowsChildWithType( JcrNodeType childPrimaryType ) {
242         if (childPrimaryType == null) {
243             // The definition must have a default primary type ...
244             if (defaultPrimaryTypeName != null) {
245                 return true;
246             }
247             return false;
248         }
249         ensureRequiredPrimaryTypesLoaded();
250         // The supplied primary type must be or extend all of the required primary types ...
251         for (Name requiredPrimaryTypeName : requiredPrimaryTypesByName.keySet()) {
252             if (!childPrimaryType.isNodeType(requiredPrimaryTypeName)) return false;
253         }
254         return true;
255     }
256 
257     /**
258      * Creates a new <code>JcrNodeDefinition</code> that is identical to the current object, but with the given
259      * <code>declaringNodeType</code>. Provided to support immutable pattern for this class.
260      * 
261      * @param declaringNodeType the declaring node type for the new <code>JcrNodeDefinition</code>
262      * @return a new <code>JcrNodeDefinition</code> that is identical to the current object, but with the given
263      *         <code>declaringNodeType</code>.
264      */
265     JcrNodeDefinition with( JcrNodeType declaringNodeType ) {
266         return new JcrNodeDefinition(this.context, declaringNodeType.nodeTypeManager(), declaringNodeType, name,
267                                      getOnParentVersion(), isAutoCreated(), isMandatory(), isProtected(),
268                                      allowsSameNameSiblings(), defaultPrimaryTypeName, requiredPrimaryTypeNames);
269     }
270 
271     JcrNodeDefinition with( ExecutionContext context ) {
272         return new JcrNodeDefinition(context, this.nodeTypeManager, this.declaringNodeType, name, getOnParentVersion(),
273                                      isAutoCreated(), isMandatory(), isProtected(), allowsSameNameSiblings(),
274                                      defaultPrimaryTypeName, requiredPrimaryTypeNames);
275     }
276 
277     JcrNodeDefinition with( RepositoryNodeTypeManager nodeTypeManager ) {
278         return new JcrNodeDefinition(this.context, nodeTypeManager, this.declaringNodeType, name, getOnParentVersion(),
279                                      isAutoCreated(), isMandatory(), isProtected(), allowsSameNameSiblings(),
280                                      defaultPrimaryTypeName, requiredPrimaryTypeNames);
281     }
282 
283     @Override
284     public int hashCode() {
285         return getId().toString().hashCode();
286     }
287 
288     @Override
289     public boolean equals( Object obj ) {
290         if (this == obj) return true;
291         if (obj == null) return false;
292         if (getClass() != obj.getClass()) return false;
293         JcrNodeDefinition other = (JcrNodeDefinition)obj;
294         if (id == null) {
295             if (other.id != null) return false;
296         } else if (!id.equals(other.id)) return false;
297         return true;
298     }
299 
300     /**
301      * {@inheritDoc}
302      * 
303      * @see java.lang.Object#toString()
304      */
305     @Override
306     public String toString() {
307         ValueFactory<String> strings = context.getValueFactories().getStringFactory();
308         StringBuilder sb = new StringBuilder();
309         NodeDefinitionId id = getId();
310         sb.append(strings.create(id.getNodeTypeName()));
311         sb.append('/');
312         sb.append(strings.create(id.getChildDefinitionName()));
313         if (id.hasRequiredPrimaryTypes()) {
314             sb.append(" (required primary types = [");
315             boolean first = true;
316             for (Name requiredPrimaryType : id.getRequiredPrimaryTypes()) {
317                 if (first) first = false;
318                 else sb.append(',');
319                 sb.append(requiredPrimaryType.getString());
320             }
321             sb.append("])");
322         }
323         return sb.toString();
324     }
325 }