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}