001/* 002 * ModeShape (http://www.modeshape.org) 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.modeshape.jdbc.rest; 017 018import static org.modeshape.jdbc.rest.JSONHelper.valueFrom; 019import static org.modeshape.jdbc.rest.JSONHelper.valuesFrom; 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.Iterator; 025import java.util.List; 026import java.util.Map; 027import java.util.NoSuchElementException; 028import java.util.Set; 029import javax.jcr.Value; 030import javax.jcr.nodetype.NodeDefinition; 031import javax.jcr.nodetype.NodeTypeIterator; 032import org.codehaus.jettison.json.JSONException; 033import org.codehaus.jettison.json.JSONObject; 034import org.modeshape.common.annotation.Immutable; 035 036 037/** 038 * Implementation of {@link javax.jcr.nodetype.NodeType} for the ModeShape client. 039 */ 040@Immutable 041public class NodeType implements javax.jcr.nodetype.NodeType { 042 043 private final String name; 044 private final boolean isAbstract; 045 private final boolean isMixin; 046 private final boolean isQueryable; 047 private final boolean hasOrderableChildNodes; 048 private final String primaryItemName; 049 private final Map<PropertyDefinition.Id, PropertyDefinition> propertyDefinitions; 050 private final Map<ChildNodeDefinition.Id, ChildNodeDefinition> childNodeDefinitions; 051 private final List<String> declaredSuperTypes; 052 private final NodeTypes nodeTypes; 053 private List<NodeType> allSuperTypes; 054 private Set<String> allSuperTypeNames; 055 private Map<PropertyDefinition.Id, PropertyDefinition> allPropertyDefinitions; 056 private Map<ChildNodeDefinition.Id, ChildNodeDefinition> allChildNodeDefinitions; 057 058 @SuppressWarnings("unchecked") 059 protected NodeType(JSONObject json, NodeTypes nodeTypes) { 060 this.nodeTypes = nodeTypes; 061 062 this.name = valueFrom(json, "jcr:nodeTypeName"); 063 assert this.name != null; 064 this.isMixin = valueFrom(json, "jcr:isMixin", false); 065 this.isAbstract = valueFrom(json, "jcr:isAbstract", false); 066 this.hasOrderableChildNodes = valueFrom(json, "jcr:hasOrderableChildNodes", false); 067 this.isQueryable = valueFrom(json, "jcr:isQueryable", false); 068 this.primaryItemName = valueFrom(json, "jcr:primaryItemName"); 069 this.declaredSuperTypes = valuesFrom(json, "jcr:supertypes"); 070 071 this.propertyDefinitions = new HashMap<>(); 072 this.childNodeDefinitions = new HashMap<>(); 073 074 // Process the children (the property definition and child node definition objects) ... 075 if (json.has("children")) { 076 try { 077 JSONObject children = json.getJSONObject("children"); 078 for (Iterator<String> itr = children.keys(); itr.hasNext(); ) { 079 String key = itr.next(); 080 JSONObject child = children.getJSONObject(key); 081 if (child != null) { 082 // Get the primary type of this child object ... 083 String type = getPrimaryType(key, child); 084 if (type.startsWith("nt:propertyDefinition") || type.startsWith("jcr:propertyDefinition")) { 085 PropertyDefinition defn = new PropertyDefinition(name, child, this.nodeTypes); 086 propertyDefinitions.put(defn.id(), defn); 087 } else if (type.startsWith("nt:childNodeDefinition") || type.startsWith( 088 "jcr:childNodeDefinition")) { 089 ChildNodeDefinition defn = new ChildNodeDefinition(name, child, this.nodeTypes); 090 childNodeDefinitions.put(defn.id(), defn); 091 } 092 } 093 } 094 } catch (JSONException e) { 095 throw new RuntimeException(e); 096 } 097 } 098 } 099 100 @Override 101 public String getName() { 102 return this.name; 103 } 104 105 @Override 106 public javax.jcr.nodetype.NodeType[] getDeclaredSupertypes() { 107 return nodeTypes.toNodeTypes(declaredSuperTypes); 108 } 109 110 @Override 111 public javax.jcr.nodetype.NodeType[] getSupertypes() { 112 List<NodeType> allSuperTypes = allSuperTypes(); 113 return allSuperTypes.toArray(new javax.jcr.nodetype.NodeType[allSuperTypes.size()]); 114 } 115 116 @Override 117 public String[] getDeclaredSupertypeNames() { 118 return declaredSuperTypes.toArray(new String[declaredSuperTypes.size()]); 119 } 120 121 @Override 122 public NodeTypeIterator getDeclaredSubtypes() { 123 List<NodeType> results = new ArrayList<>(); 124 for (NodeType nodeType : nodeTypes.nodeTypes()) { 125 if (nodeType == this) continue; 126 if (nodeType.declaredSuperTypes.contains(name)) { 127 results.add(nodeType); 128 } 129 } 130 return iterator(results); 131 } 132 133 @Override 134 public NodeTypeIterator getSubtypes() { 135 List<NodeType> results = new ArrayList<>(); 136 for (NodeType nodeType : nodeTypes.nodeTypes()) { 137 if (nodeType == this) continue; 138 if (nodeType.allSuperTypeNames().contains(name)) { 139 results.add(nodeType); 140 } 141 } 142 return iterator(results); 143 } 144 145 protected List<NodeType> allSuperTypes() { 146 if (this.allSuperTypes == null) { 147 List<NodeType> allSuperTypes = new ArrayList<>(); 148 Set<String> allSuperTypeNames = new HashSet<>(); 149 for (String superTypeName : declaredSuperTypes) { 150 NodeType superType = nodeTypes.getNodeType(superTypeName); 151 if (superType != null) { 152 allSuperTypes.add(superType); 153 allSuperTypeNames.add(superType.getName()); 154 // Add all of the supertypes ... 155 allSuperTypes.addAll(superType.allSuperTypes()); 156 allSuperTypeNames.addAll(superType.allSuperTypeNames()); 157 } 158 } 159 if (allSuperTypes.isEmpty() && !isMixin) { 160 // All non-mixin node types ultimately extend 'nt:base' ... 161 NodeType ntBase = nodeTypes.getNodeType("nt:base"); 162 if (ntBase != null) { 163 allSuperTypes.add(ntBase); 164 allSuperTypeNames.add(ntBase.getName()); 165 } 166 } 167 this.allSuperTypes = allSuperTypes; 168 this.allSuperTypeNames = allSuperTypeNames; 169 } 170 return this.allSuperTypes; 171 } 172 173 protected Set<String> allSuperTypeNames() { 174 allSuperTypes(); 175 return allSuperTypeNames; 176 } 177 178 @Override 179 public NodeDefinition[] getDeclaredChildNodeDefinitions() { 180 return childNodeDefinitions.values().toArray(new NodeDefinition[childNodeDefinitions.size()]); 181 } 182 183 @Override 184 public NodeDefinition[] getChildNodeDefinitions() { 185 Collection<ChildNodeDefinition> allDefns = allChildNodeDefinitions(); 186 return allDefns.toArray(new NodeDefinition[allDefns.size()]); 187 } 188 189 protected Collection<ChildNodeDefinition> declaredChildNodeDefinitions() { 190 return childNodeDefinitions.values(); 191 } 192 193 protected Collection<ChildNodeDefinition> allChildNodeDefinitions() { 194 if (this.allChildNodeDefinitions == null) { 195 Map<ChildNodeDefinition.Id, ChildNodeDefinition> allDefns = new HashMap<>(); 196 // Add the declared child node definitions for this node ... 197 allDefns.putAll(childNodeDefinitions); 198 for (NodeType superType : allSuperTypes()) { 199 for (ChildNodeDefinition childDefn : superType.declaredChildNodeDefinitions()) { 200 if (!allDefns.containsKey(childDefn.id())) { 201 allDefns.put(childDefn.id(), childDefn); 202 } 203 } 204 } 205 this.allChildNodeDefinitions = allDefns; 206 } 207 return this.allChildNodeDefinitions.values(); 208 } 209 210 @Override 211 public javax.jcr.nodetype.PropertyDefinition[] getDeclaredPropertyDefinitions() { 212 return propertyDefinitions.values().toArray(new javax.jcr.nodetype.PropertyDefinition[propertyDefinitions.size()]); 213 } 214 215 216 @Override 217 public javax.jcr.nodetype.PropertyDefinition[] getPropertyDefinitions() { 218 Collection<PropertyDefinition> allDefns = allPropertyDefinitions(); 219 return allDefns.toArray(new javax.jcr.nodetype.PropertyDefinition[allDefns.size()]); 220 } 221 222 protected Collection<PropertyDefinition> declaredPropertyDefinitions() { 223 return propertyDefinitions.values(); 224 } 225 226 protected Collection<PropertyDefinition> allPropertyDefinitions() { 227 if (this.allPropertyDefinitions == null) { 228 Map<PropertyDefinition.Id, PropertyDefinition> allDefns = new HashMap<>(); 229 // Add the declared child node definitions for this node ... 230 allDefns.putAll(propertyDefinitions); 231 for (NodeType superType : allSuperTypes()) { 232 for (PropertyDefinition propDefn : superType.declaredPropertyDefinitions()) { 233 if (!allDefns.containsKey(propDefn.id())) { 234 allDefns.put(propDefn.id(), propDefn); 235 } 236 } 237 } 238 this.allPropertyDefinitions = allDefns; 239 } 240 return this.allPropertyDefinitions.values(); 241 } 242 243 protected String getPrimaryType( String key, 244 JSONObject child ) { 245 // older versions used to have "jcr:propertyDefinition" or "jcr:childNodeDefinition" as key 246 try { 247 String primaryType = child.getString("jcr:primaryType"); 248 return primaryType != null ? primaryType : key; 249 } catch (JSONException e) { 250 throw new RuntimeException(e); 251 } 252 } 253 254 @Override 255 public String getPrimaryItemName() { 256 return primaryItemName; 257 } 258 259 @Override 260 public boolean hasOrderableChildNodes() { 261 return hasOrderableChildNodes; 262 } 263 264 @Override 265 public boolean isAbstract() { 266 return isAbstract; 267 } 268 269 @Override 270 public boolean isMixin() { 271 return isMixin; 272 } 273 274 @Override 275 public boolean isQueryable() { 276 return isQueryable; 277 } 278 279 @Override 280 public boolean isNodeType( String nodeTypeName ) { 281 if (nodeTypeName == null) return false; 282 if (this.name.equals(nodeTypeName)) return true; 283 return allSuperTypeNames().contains(nodeTypeName); 284 } 285 286 @Override 287 public boolean canAddChildNode( String childNodeName ) { 288 return false; 289 } 290 291 @Override 292 public boolean canAddChildNode( String childNodeName, 293 String nodeTypeName ) { 294 return false; 295 } 296 297 @Override 298 public boolean canRemoveItem( String itemName ) { 299 return false; 300 } 301 302 @Override 303 public boolean canRemoveNode( String nodeName ) { 304 return false; 305 } 306 307 @Override 308 public boolean canRemoveProperty( String propertyName ) { 309 return false; 310 } 311 312 @Override 313 public boolean canSetProperty( String propertyName, 314 Value value ) { 315 return false; 316 } 317 318 @Override 319 public boolean canSetProperty( String propertyName, 320 Value[] values ) { 321 return false; 322 } 323 @Override 324 public int hashCode() { 325 return name.hashCode(); 326 } 327 328 @Override 329 public boolean equals( Object obj ) { 330 if (this == obj) return true; 331 if (obj instanceof NodeType) { 332 return this.name.equals(((NodeType) obj).name); 333 } 334 return false; 335 } 336 337 @Override 338 public String toString() { 339 StringBuilder sb = new StringBuilder(); 340 sb.append('['); 341 sb.append(name); 342 sb.append(']'); 343 if (getDeclaredSupertypeNames().length != 0) { 344 sb.append(" > "); 345 boolean first = true; 346 for (String typeName : getDeclaredSupertypeNames()) { 347 if (typeName == null) continue; 348 if (first) first = false; 349 else sb.append(','); 350 sb.append(typeName); 351 } 352 } 353 if (isAbstract()) sb.append(" abstract"); 354 if (isMixin()) sb.append(" mixin"); 355 if (!isQueryable()) sb.append(" noquery"); 356 if (hasOrderableChildNodes()) sb.append(" orderable"); 357 if (getPrimaryItemName() != null) { 358 sb.append(" primaryitem ").append(getPrimaryItemName()); 359 } 360 for (PropertyDefinition propDefn : declaredPropertyDefinitions()) { 361 sb.append('\n').append(propDefn); 362 } 363 for (ChildNodeDefinition childDefn : declaredChildNodeDefinitions()) { 364 sb.append('\n').append(childDefn); 365 } 366 sb.append('\n'); 367 return sb.toString(); 368 } 369 370 private NodeTypeIterator iterator(final List<NodeType> nodeTypes) { 371 return new NodeTypeIterator() { 372 private int position = 0; 373 private Iterator<NodeType> iterator = nodeTypes.iterator(); 374 375 @Override 376 public javax.jcr.nodetype.NodeType nextNodeType() { 377 NodeType nodeType = iterator.next(); 378 position++; 379 return nodeType; 380 } 381 382 @Override 383 public void skip( long skipNum ) { 384 for (int i = 0; i < skipNum; i++) { 385 position++; 386 if (position >= getSize()) { 387 throw new NoSuchElementException(); 388 } 389 nextNodeType(); 390 } 391 } 392 393 @Override 394 public long getSize() { 395 return nodeTypes.size(); 396 } 397 398 @Override 399 public long getPosition() { 400 return position; 401 } 402 403 @Override 404 public boolean hasNext() { 405 return iterator.hasNext(); 406 } 407 408 @Override 409 public Object next() { 410 return iterator.next(); 411 } 412 413 @Override 414 public void remove() { 415 throw new UnsupportedOperationException(); 416 } 417 }; 418 } 419}