/* * ModeShape (http://www.modeshape.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.modeshape.jdbc.rest; import static org.modeshape.jdbc.rest.JSONHelper.valueFrom; import static org.modeshape.jdbc.rest.JSONHelper.valuesFrom; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import javax.jcr.Value; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeTypeIterator; import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; import org.modeshape.common.annotation.Immutable; /** * Implementation of {@link javax.jcr.nodetype.NodeType} for the ModeShape client. */ @Immutable public class NodeType implements javax.jcr.nodetype.NodeType { private final String name; private final boolean isAbstract; private final boolean isMixin; private final boolean isQueryable; private final boolean hasOrderableChildNodes; private final String primaryItemName; private final Map<PropertyDefinition.Id, PropertyDefinition> propertyDefinitions; private final Map<ChildNodeDefinition.Id, ChildNodeDefinition> childNodeDefinitions; private final List<String> declaredSuperTypes; private final NodeTypes nodeTypes; private List<NodeType> allSuperTypes; private Set<String> allSuperTypeNames; private Map<PropertyDefinition.Id, PropertyDefinition> allPropertyDefinitions; private Map<ChildNodeDefinition.Id, ChildNodeDefinition> allChildNodeDefinitions; @SuppressWarnings("unchecked") protected NodeType(JSONObject json, NodeTypes nodeTypes) { this.nodeTypes = nodeTypes; this.name = valueFrom(json, "jcr:nodeTypeName"); assert this.name != null; this.isMixin = valueFrom(json, "jcr:isMixin", false); this.isAbstract = valueFrom(json, "jcr:isAbstract", false); this.hasOrderableChildNodes = valueFrom(json, "jcr:hasOrderableChildNodes", false); this.isQueryable = valueFrom(json, "jcr:isQueryable", false); this.primaryItemName = valueFrom(json, "jcr:primaryItemName"); this.declaredSuperTypes = valuesFrom(json, "jcr:supertypes"); this.propertyDefinitions = new HashMap<>(); this.childNodeDefinitions = new HashMap<>(); // Process the children (the property definition and child node definition objects) ... if (json.has("children")) { try { JSONObject children = json.getJSONObject("children"); for (Iterator<String> itr = children.keys(); itr.hasNext(); ) { String key = itr.next(); JSONObject child = children.getJSONObject(key); if (child != null) { // Get the primary type of this child object ... String type = getPrimaryType(key, child); if (type.startsWith("nt:propertyDefinition") || type.startsWith("jcr:propertyDefinition")) { PropertyDefinition defn = new PropertyDefinition(name, child, this.nodeTypes); propertyDefinitions.put(defn.id(), defn); } else if (type.startsWith("nt:childNodeDefinition") || type.startsWith( "jcr:childNodeDefinition")) { ChildNodeDefinition defn = new ChildNodeDefinition(name, child, this.nodeTypes); childNodeDefinitions.put(defn.id(), defn); } } } } catch (JSONException e) { throw new RuntimeException(e); } } } @Override public String getName() { return this.name; } @Override public javax.jcr.nodetype.NodeType[] getDeclaredSupertypes() { return nodeTypes.toNodeTypes(declaredSuperTypes); } @Override public javax.jcr.nodetype.NodeType[] getSupertypes() { List<NodeType> allSuperTypes = allSuperTypes(); return allSuperTypes.toArray(new javax.jcr.nodetype.NodeType[allSuperTypes.size()]); } @Override public String[] getDeclaredSupertypeNames() { return declaredSuperTypes.toArray(new String[declaredSuperTypes.size()]); } @Override public NodeTypeIterator getDeclaredSubtypes() { List<NodeType> results = new ArrayList<>(); for (NodeType nodeType : nodeTypes.nodeTypes()) { if (nodeType == this) continue; if (nodeType.declaredSuperTypes.contains(name)) { results.add(nodeType); } } return iterator(results); } @Override public NodeTypeIterator getSubtypes() { List<NodeType> results = new ArrayList<>(); for (NodeType nodeType : nodeTypes.nodeTypes()) { if (nodeType == this) continue; if (nodeType.allSuperTypeNames().contains(name)) { results.add(nodeType); } } return iterator(results); } protected List<NodeType> allSuperTypes() { if (this.allSuperTypes == null) { List<NodeType> allSuperTypes = new ArrayList<>(); Set<String> allSuperTypeNames = new HashSet<>(); for (String superTypeName : declaredSuperTypes) { NodeType superType = nodeTypes.getNodeType(superTypeName); if (superType != null) { allSuperTypes.add(superType); allSuperTypeNames.add(superType.getName()); // Add all of the supertypes ... allSuperTypes.addAll(superType.allSuperTypes()); allSuperTypeNames.addAll(superType.allSuperTypeNames()); } } if (allSuperTypes.isEmpty() && !isMixin) { // All non-mixin node types ultimately extend 'nt:base' ... NodeType ntBase = nodeTypes.getNodeType("nt:base"); if (ntBase != null) { allSuperTypes.add(ntBase); allSuperTypeNames.add(ntBase.getName()); } } this.allSuperTypes = allSuperTypes; this.allSuperTypeNames = allSuperTypeNames; } return this.allSuperTypes; } protected Set<String> allSuperTypeNames() { allSuperTypes(); return allSuperTypeNames; } @Override public NodeDefinition[] getDeclaredChildNodeDefinitions() { return childNodeDefinitions.values().toArray(new NodeDefinition[childNodeDefinitions.size()]); } @Override public NodeDefinition[] getChildNodeDefinitions() { Collection<ChildNodeDefinition> allDefns = allChildNodeDefinitions(); return allDefns.toArray(new NodeDefinition[allDefns.size()]); } protected Collection<ChildNodeDefinition> declaredChildNodeDefinitions() { return childNodeDefinitions.values(); } protected Collection<ChildNodeDefinition> allChildNodeDefinitions() { if (this.allChildNodeDefinitions == null) { Map<ChildNodeDefinition.Id, ChildNodeDefinition> allDefns = new HashMap<>(); // Add the declared child node definitions for this node ... allDefns.putAll(childNodeDefinitions); for (NodeType superType : allSuperTypes()) { for (ChildNodeDefinition childDefn : superType.declaredChildNodeDefinitions()) { if (!allDefns.containsKey(childDefn.id())) { allDefns.put(childDefn.id(), childDefn); } } } this.allChildNodeDefinitions = allDefns; } return this.allChildNodeDefinitions.values(); } @Override public javax.jcr.nodetype.PropertyDefinition[] getDeclaredPropertyDefinitions() { return propertyDefinitions.values().toArray(new javax.jcr.nodetype.PropertyDefinition[propertyDefinitions.size()]); } @Override public javax.jcr.nodetype.PropertyDefinition[] getPropertyDefinitions() { Collection<PropertyDefinition> allDefns = allPropertyDefinitions(); return allDefns.toArray(new javax.jcr.nodetype.PropertyDefinition[allDefns.size()]); } protected Collection<PropertyDefinition> declaredPropertyDefinitions() { return propertyDefinitions.values(); } protected Collection<PropertyDefinition> allPropertyDefinitions() { if (this.allPropertyDefinitions == null) { Map<PropertyDefinition.Id, PropertyDefinition> allDefns = new HashMap<>(); // Add the declared child node definitions for this node ... allDefns.putAll(propertyDefinitions); for (NodeType superType : allSuperTypes()) { for (PropertyDefinition propDefn : superType.declaredPropertyDefinitions()) { if (!allDefns.containsKey(propDefn.id())) { allDefns.put(propDefn.id(), propDefn); } } } this.allPropertyDefinitions = allDefns; } return this.allPropertyDefinitions.values(); } protected String getPrimaryType( String key, JSONObject child ) { // older versions used to have "jcr:propertyDefinition" or "jcr:childNodeDefinition" as key try { String primaryType = child.getString("jcr:primaryType"); return primaryType != null ? primaryType : key; } catch (JSONException e) { throw new RuntimeException(e); } } @Override public String getPrimaryItemName() { return primaryItemName; } @Override public boolean hasOrderableChildNodes() { return hasOrderableChildNodes; } @Override public boolean isAbstract() { return isAbstract; } @Override public boolean isMixin() { return isMixin; } @Override public boolean isQueryable() { return isQueryable; } @Override public boolean isNodeType( String nodeTypeName ) { if (nodeTypeName == null) return false; if (this.name.equals(nodeTypeName)) return true; return allSuperTypeNames().contains(nodeTypeName); } @Override public boolean canAddChildNode( String childNodeName ) { return false; } @Override public boolean canAddChildNode( String childNodeName, String nodeTypeName ) { return false; } @Override public boolean canRemoveItem( String itemName ) { return false; } @Override public boolean canRemoveNode( String nodeName ) { return false; } @Override public boolean canRemoveProperty( String propertyName ) { return false; } @Override public boolean canSetProperty( String propertyName, Value value ) { return false; } @Override public boolean canSetProperty( String propertyName, Value[] values ) { return false; } @Override public int hashCode() { return name.hashCode(); } @Override public boolean equals( Object obj ) { if (this == obj) return true; if (obj instanceof NodeType) { return this.name.equals(((NodeType) obj).name); } return false; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append('['); sb.append(name); sb.append(']'); if (getDeclaredSupertypeNames().length != 0) { sb.append(" > "); boolean first = true; for (String typeName : getDeclaredSupertypeNames()) { if (typeName == null) continue; if (first) first = false; else sb.append(','); sb.append(typeName); } } if (isAbstract()) sb.append(" abstract"); if (isMixin()) sb.append(" mixin"); if (!isQueryable()) sb.append(" noquery"); if (hasOrderableChildNodes()) sb.append(" orderable"); if (getPrimaryItemName() != null) { sb.append(" primaryitem ").append(getPrimaryItemName()); } for (PropertyDefinition propDefn : declaredPropertyDefinitions()) { sb.append('\n').append(propDefn); } for (ChildNodeDefinition childDefn : declaredChildNodeDefinitions()) { sb.append('\n').append(childDefn); } sb.append('\n'); return sb.toString(); } private NodeTypeIterator iterator(final List<NodeType> nodeTypes) { return new NodeTypeIterator() { private int position = 0; private Iterator<NodeType> iterator = nodeTypes.iterator(); @Override public javax.jcr.nodetype.NodeType nextNodeType() { NodeType nodeType = iterator.next(); position++; return nodeType; } @Override public void skip( long skipNum ) { for (int i = 0; i < skipNum; i++) { position++; if (position >= getSize()) { throw new NoSuchElementException(); } nextNodeType(); } } @Override public long getSize() { return nodeTypes.size(); } @Override public long getPosition() { return position; } @Override public boolean hasNext() { return iterator.hasNext(); } @Override public Object next() { return iterator.next(); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } }