Return-Path: Delivered-To: apmail-incubator-jackrabbit-commits-archive@www.apache.org Received: (qmail 34193 invoked from network); 30 Jan 2006 14:08:31 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur.apache.org with SMTP; 30 Jan 2006 14:08:31 -0000 Received: (qmail 59691 invoked by uid 500); 30 Jan 2006 14:08:31 -0000 Mailing-List: contact jackrabbit-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: jackrabbit-dev@incubator.apache.org Delivered-To: mailing list jackrabbit-commits@incubator.apache.org Received: (qmail 59680 invoked by uid 500); 30 Jan 2006 14:08:31 -0000 Delivered-To: apmail-incubator-jackrabbit-cvs@incubator.apache.org Received: (qmail 59677 invoked by uid 99); 30 Jan 2006 14:08:31 -0000 Received: from asf.osuosl.org (HELO asf.osuosl.org) (140.211.166.49) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 30 Jan 2006 06:08:31 -0800 X-ASF-Spam-Status: No, hits=-9.4 required=10.0 tests=ALL_TRUSTED,NO_REAL_NAME X-Spam-Check-By: apache.org Received: from [209.237.227.194] (HELO minotaur.apache.org) (209.237.227.194) by apache.org (qpsmtpd/0.29) with SMTP; Mon, 30 Jan 2006 06:08:28 -0800 Received: (qmail 33942 invoked by uid 65534); 30 Jan 2006 14:08:08 -0000 Message-ID: <20060130140808.33937.qmail@minotaur.apache.org> Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r373516 - in /incubator/jackrabbit/trunk/jackrabbit/src: main/java/org/apache/jackrabbit/core/nodetype/ test/java/org/apache/jackrabbit/core/nodetype/ Date: Mon, 30 Jan 2006 14:08:06 -0000 To: jackrabbit-cvs@incubator.apache.org From: stefan@apache.org X-Mailer: svnmailer-1.0.5 X-Virus-Checked: Checked by ClamAV on apache.org X-Spam-Rating: minotaur.apache.org 1.6.2 0/1000/N Author: stefan Date: Mon Jan 30 06:07:41 2006 New Revision: 373516 URL: http://svn.apache.org/viewcvs?rev=373516&view=rev Log: JCR-274: committing sandro's patch with some modifications (mainly formatting related) thanks sandro! Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeTypeCache.java (with props) incubator/jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/nodetype/CyclicNodeTypeRegistrationTest.java (with props) Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeType.java incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeRegistry.java Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeType.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeType.java?rev=373516&r1=373515&r2=373516&view=diff ============================================================================== --- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeType.java (original) +++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeType.java Mon Jan 30 06:07:41 2006 @@ -25,11 +25,12 @@ import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.TreeSet; -import java.util.Arrays; /** * An EffectiveNodeType represents one or more @@ -69,8 +70,8 @@ } /** - * Factory method: creates an effective node type - * representation of an existing (i.e. registered) node type. + * Factory method: creates an effective node type representation of an + * existing (i.e. registered) node type. * * @param ntReg * @param nodeTypeName @@ -84,10 +85,10 @@ } /** - * Factory method: creates an effective node type - * representation of a node type definition. Whereas all referenced - * node types must exist (i.e. must be registered), the definition itself - * is not required to be registered. + * Factory method: creates an effective node type representation of a + * node type definition. Whereas all referenced node types must exist + * (i.e. must be registered), the definition itself is not required to be + * registered. * * @param ntReg * @param ntd @@ -97,6 +98,29 @@ */ public static EffectiveNodeType create(NodeTypeRegistry ntReg, NodeTypeDef ntd) throws NodeTypeConflictException, NoSuchNodeTypeException { + return create(ntReg, ntd, null, null); + } + + /** + * Package private factory method. + *

+ * Creates an effective node type representation of a node type definition. + * Whereas all referenced node types must exist (i.e. must be registered), + * the definition itself is not required to be registered. + * todo check javadoc/param names + * @param ntReg + * @param ntd + * @param anEntCache + * @param aRegisteredNTDefCache + * @return + * @throws NodeTypeConflictException + * @throws NoSuchNodeTypeException + */ + static EffectiveNodeType create(NodeTypeRegistry ntReg, + NodeTypeDef ntd, + EffectiveNodeTypeCache anEntCache, + Map aRegisteredNTDefCache) + throws NodeTypeConflictException, NoSuchNodeTypeException { // create empty effective node type instance EffectiveNodeType ent = new EffectiveNodeType(ntReg); QName ntName = ntd.getName(); @@ -210,7 +234,11 @@ // resolve supertypes recursively QName[] supertypes = ntd.getSupertypes(); if (supertypes != null && supertypes.length > 0) { - ent.internalMerge(ntReg.getEffectiveNodeType(supertypes), true); + if (anEntCache == null || aRegisteredNTDefCache == null) { + ent.internalMerge(ntReg.getEffectiveNodeType(supertypes), true); + } else { + ent.internalMerge(ntReg.getEffectiveNodeType(supertypes, anEntCache, aRegisteredNTDefCache), true); + } } // we're done @@ -218,9 +246,10 @@ } /** - * Factory method: creates a new 'empty' effective node type instance + * Package private factory method for creating a new 'empty' effective + * node type instance. * - * @return + * @return an 'empty' effective node type instance. */ static EffectiveNodeType create(NodeTypeRegistry ntReg) { return new EffectiveNodeType(ntReg); @@ -905,7 +934,8 @@ * @throws ConstraintViolationException * @throws NoSuchNodeTypeException */ - public void checkRequiredPrimaryType(QName nodeTypeName, QName[] requiredPrimaryTypes) + public void checkRequiredPrimaryType(QName nodeTypeName, + QName[] requiredPrimaryTypes) throws ConstraintViolationException, NoSuchNodeTypeException { if (requiredPrimaryTypes == null) { // no constraint Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeTypeCache.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeTypeCache.java?rev=373516&view=auto ============================================================================== --- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeTypeCache.java (added) +++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeTypeCache.java Mon Jan 30 06:07:41 2006 @@ -0,0 +1,296 @@ +/* + * Copyright 2004-2005 The Apache Software Foundation or its licensors, + * as applicable. + * + * 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.apache.jackrabbit.core.nodetype; + +import org.apache.jackrabbit.core.util.Dumpable; +import org.apache.jackrabbit.name.QName; + +import java.io.PrintStream; +import java.util.HashMap; +import java.util.Iterator; +import java.util.TreeSet; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +/** + * EffectiveNodeTypeCache ... + */ +class EffectiveNodeTypeCache implements Dumpable { + /** ordered set of keys */ + final TreeSet sortedKeys; + /** cache of pre-built aggregations of node types */ + final HashMap aggregates; + + EffectiveNodeTypeCache() { + sortedKeys = new TreeSet(); + aggregates = new HashMap(); + } + + EffectiveNodeTypeCache(EffectiveNodeTypeCache entCache) { + sortedKeys = new TreeSet(entCache.sortedKeys); + aggregates = new HashMap(entCache.aggregates); + } + + void put(EffectiveNodeType ent) { + // we define the weight as the total number of included node types + // (through aggregation and inheritance) + int weight = ent.getAllNodeTypes().length; + // the effective node type is identified by the list of merged + // (i.e. aggregated) node types + WeightedKey k = new WeightedKey(ent.getMergedNodeTypes(), weight); + aggregates.put(k, ent); + sortedKeys.add(k); + } + + boolean contains(QName[] ntNames) { + return aggregates.containsKey(new WeightedKey(ntNames)); + } + + boolean contains(WeightedKey key) { + return aggregates.containsKey(key); + } + + EffectiveNodeType get(QName[] ntNames) { + return (EffectiveNodeType) aggregates.get(new WeightedKey(ntNames)); + } + + EffectiveNodeType get(WeightedKey key) { + return (EffectiveNodeType) aggregates.get(key); + } + + EffectiveNodeType remove(QName[] ntNames) { + return remove(new WeightedKey(ntNames)); + } + + EffectiveNodeType remove(WeightedKey key) { + EffectiveNodeType removed = (EffectiveNodeType) aggregates.remove(key); + if (removed != null) { + // remove index entry + + // FIXME: can't simply call TreeSet.remove(key) because the entry + // in sortedKeys might have a different weight and would thus + // not be found + Iterator iter = sortedKeys.iterator(); + while (iter.hasNext()) { + WeightedKey k = (WeightedKey) iter.next(); + // WeightedKey.equals(Object) ignores the weight + if (key.equals(k)) { + sortedKeys.remove(k); + break; + } + } + } + return removed; + } + + /** + * Returns an iterator over the keys. The order of the returned keys is: + *

    + *
  1. descending weight
  2. + *
  3. ascending key (i.e. unique identifier of aggregate)
  4. + *
+ * + * @see WeightedKey#compareTo + */ + Iterator keys() { + return sortedKeys.iterator(); + } + + //-------------------------------------------------------------< Dumpable > + /** + * {@inheritDoc} + */ + public void dump(PrintStream ps) { + ps.println("EffectiveNodeTypeCache (" + this + ")"); + ps.println(); + ps.println("EffectiveNodeTypes in cache:"); + ps.println(); + Iterator iter = sortedKeys.iterator(); + while (iter.hasNext()) { + WeightedKey k = (WeightedKey) iter.next(); + //EffectiveNodeType ent = (EffectiveNodeType) aggregates.get(k); + ps.println(k); + } + } + + //--------------------------------------------------------< inner classes > + /** + * A WeightedKey uniquely identifies + * a combination (i.e. an aggregation) of one or more node types. + * The weight is an indicator for the cost involved in building such an + * aggregate (e.g. an aggregation of multiple complex node types with deep + * inheritance trees is more costly to build/validate than an agreggation + * of two very simple node types with just one property definition each). + *

+ * A very simple (and not very accurate) approximation of the weight would + * be the number of explicitly aggregated node types (ignoring inheritance + * and complexity of each involved node type). A better approximation would + * be the number of all, explicitly and implicitly (note that + * inheritance is also an aggregation) aggregated node types. + *

+ * The more accurate the weight definition, the more efficient is the + * the building of new aggregates. + *

+ * It is important to note that the weight is not part of the key value, + * i.e. it is not considered by the hashCode() and + * equals(Object) methods. It does however affect the order + * of WeightedKey instances. See + * {@link #compareTo(Object)} for more information. + *

+ * Let's assume we have an aggregation of node types named "b", "a" and "c". + * Its key would be "[a, b, c]" and the weight 3 (using the simple + * approximation). + */ + static class WeightedKey implements Comparable { + /** + * set of node type names, sorted in ascending order + */ + private final TreeSet set; + private final int weight; + + /** + * @param ntNames + */ + WeightedKey(QName[] ntNames) { + this(ntNames, ntNames.length); + } + + /** + * @param ntNames + * @param weight + */ + WeightedKey(QName[] ntNames, int weight) { + this.weight = weight; + + set = new TreeSet(); + for (int i = 0; i < ntNames.length; i++) { + // add name to this sorted set + set.add(ntNames[i]); + } + } + + /** + * @param ntNames + */ + WeightedKey(Collection ntNames) { + this(ntNames, ntNames.size()); + } + + /** + * @param ntNames + * @param weight + */ + WeightedKey(Collection ntNames, int weight) { + this.weight = weight; + set = new TreeSet(ntNames); + } + + /** + * The key is the string representation of this sorted set + * (e.g. the key for a set containing entries "c", "b" and "a" would + * be "[a, b, c]"). + * + * @return string representation of this sorted set + * @see java.util.AbstractCollection#toString + */ + String getKey() { + return set.toString(); + } + + /** + * @return + */ + int getWeight() { + return weight; + } + + int size() { + return set.size(); + } + + Iterator iterator() { + return Collections.unmodifiableSortedSet(set).iterator(); + } + + Set getSet() { + return Collections.unmodifiableSortedSet(set); + } + + QName[] toArray() { + return (QName[]) set.toArray(new QName[set.size()]); + } + + boolean contains(WeightedKey otherKey) { + return set.containsAll(otherKey.getSet()); + } + + WeightedKey subtract(WeightedKey otherKey) { + Set tmp = (Set) set.clone(); + tmp.removeAll(otherKey.getSet()); + return new WeightedKey(tmp); + + } + + /** + * The resulting sort-order is: 1. descending weight, 2. ascending key + * (i.e. string representation of this sorted set). + * + * @param o + * @return + */ + public int compareTo(Object o) { + WeightedKey other = (WeightedKey) o; + if (getWeight() > other.getWeight()) { + return -1; + } else if (getWeight() < other.getWeight()) { + return 1; + } + return getKey().compareTo(other.getKey()); + } + + public int hashCode() { + int h = 17; + // ignore weight + Iterator i = set.iterator(); + while (i.hasNext()) { + h *= 37; + Object obj = i.next(); + if (obj != null) { + h += obj.hashCode(); + } + } + return h; + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof WeightedKey) { + WeightedKey other = (WeightedKey) obj; + // ignore weight + return set.equals(other.set); + } + return false; + } + + public String toString() { + return set.toString() + " (" + weight + ")"; + } + } +} \ No newline at end of file Propchange: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeTypeCache.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeTypeCache.java ------------------------------------------------------------------------------ svn:keywords = author date id revision url Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeRegistry.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeRegistry.java?rev=373516&r1=373515&r2=373516&view=diff ============================================================================== --- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeRegistry.java (original) +++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeRegistry.java Mon Jan 30 06:07:41 2006 @@ -20,8 +20,8 @@ import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.fs.FileSystemException; import org.apache.jackrabbit.core.fs.FileSystemResource; -import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.core.util.Dumpable; +import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.name.QName; import org.apache.log4j.Logger; @@ -44,7 +44,6 @@ import java.util.Map; import java.util.Set; import java.util.Stack; -import java.util.TreeSet; /** * A NodeTypeRegistry ... @@ -206,7 +205,7 @@ // FIXME need a fake declaring node type: // rep:root is not quite correct but better than a non-existing node type def.setDeclaringNodeType(QName.REP_ROOT); - def.setRequiredPrimaryTypes(new QName[] { QName.REP_ROOT }); + def.setRequiredPrimaryTypes(new QName[]{QName.REP_ROOT}); def.setDefaultPrimaryType(QName.REP_ROOT); def.setMandatory(true); def.setProtected(false); @@ -216,56 +215,6 @@ return def; } - /** - * Validates and registers the specified collection of NodeTypeDef - * objects. An InvalidNodeTypeDefException is thrown if the - * validation of any of the contained NodeTypeDef objects fails. - *

- * Note that in the case an exception is thrown some node types might have - * been nevertheless successfully registered. - * - * @param ntDefs collection of NodeTypeDef objects - * @throws InvalidNodeTypeDefException - * @throws RepositoryException - * @see #registerNodeType - */ - private synchronized void internalRegister(Collection ntDefs) - throws InvalidNodeTypeDefException, RepositoryException { - ArrayList list = new ArrayList(ntDefs); - - // iterate over definitions until there are no more definitions with - // unresolved (i.e. unregistered) dependencies or an error occurs; - - int count = -1; // number of registered nt's per iteration - while (list.size() > 0 && count != 0) { - count = 0; - Iterator iterator = list.iterator(); - while (iterator.hasNext()) { - NodeTypeDef ntd = (NodeTypeDef) iterator.next(); - // check if definition has unresolved dependencies - if (registeredNTDefs.keySet().containsAll(ntd.getDependencies())) { - // try to register it - internalRegister(ntd); - // remove it from list - iterator.remove(); - // increase count - count++; - } - } - } - if (list.size() > 0) { - StringBuffer msg = new StringBuffer(); - msg.append("the following node types could not be registered because of unresolvable dependencies: "); - Iterator iterator = list.iterator(); - while (iterator.hasNext()) { - msg.append(((NodeTypeDef) iterator.next()).getName()); - msg.append(" "); - } - log.error(msg.toString()); - throw new InvalidNodeTypeDefException(msg.toString()); - } - } - private EffectiveNodeType internalRegister(NodeTypeDef ntd) throws InvalidNodeTypeDefException, RepositoryException { QName name = ntd.getName(); @@ -275,7 +224,7 @@ throw new InvalidNodeTypeDefException(msg); } - EffectiveNodeType ent = validateNodeTypeDef(ntd); + EffectiveNodeType ent = validateNodeTypeDef(ntd, entCache, registeredNTDefs); // store new effective node type instance entCache.put(ent); @@ -326,7 +275,8 @@ } iter = keys.iterator(); while (iter.hasNext()) { - WeightedKey k = (WeightedKey) iter.next(); + EffectiveNodeTypeCache.WeightedKey k = + (EffectiveNodeTypeCache.WeightedKey) iter.next(); EffectiveNodeType ent = entCache.get(k); if (ent.includesNodeType(name)) { entCache.remove(k); @@ -425,6 +375,7 @@ /** * Utility method for verifying that the namespace of a QName * is registered; a null argument is silently ignored. + * * @param name name whose namespace is to be checked * @throws RepositoryException if the namespace of the given name is not * registered or if an unspecified error occured @@ -436,7 +387,18 @@ } } - private EffectiveNodeType validateNodeTypeDef(NodeTypeDef ntd) + /** + * Validates the specified NodeTypeDef within the context of the two other parameter and + * returns an EffectiveNodeType. + * + * @param ntd + * @param anEntCache + * @param aRegisteredNTDefCache + * @return The EffectiveNodeType + * @throws InvalidNodeTypeDefException + * @throws RepositoryException + */ + private EffectiveNodeType validateNodeTypeDef(NodeTypeDef ntd, EffectiveNodeTypeCache anEntCache, Map aRegisteredNTDefCache) throws InvalidNodeTypeDefException, RepositoryException { /** @@ -472,7 +434,7 @@ log.debug(msg); throw new InvalidNodeTypeDefException(msg); } - if (!registeredNTDefs.containsKey(supertypes[i])) { + if (!aRegisteredNTDefCache.containsKey(supertypes[i])) { String msg = "[" + name + "] invalid supertype: " + supertypes[i]; log.debug(msg); @@ -486,7 +448,7 @@ */ Stack inheritanceChain = new Stack(); inheritanceChain.push(name); - checkForCircularInheritance(supertypes, inheritanceChain); + checkForCircularInheritance(supertypes, inheritanceChain, aRegisteredNTDefCache); } /** @@ -502,12 +464,12 @@ */ if (supertypes != null && supertypes.length > 0) { try { - EffectiveNodeType est = getEffectiveNodeType(supertypes); + EffectiveNodeType est = getEffectiveNodeType(supertypes, anEntCache, aRegisteredNTDefCache); // make sure that all primary types except nt:base extend from nt:base if (!ntd.isMixin() && !QName.NT_BASE.equals(ntd.getName()) && !est.includesNodeType(QName.NT_BASE)) { String msg = "[" + name + "] all primary node types except" - + " nt:base itself must be (directly or indirectly) derived from nt:base"; + + " nt:base itself must be (directly or indirectly) derived from nt:base"; log.debug(msg); throw new InvalidNodeTypeDefException(msg); } @@ -633,7 +595,7 @@ for (int j = 0; j < constraints.length; j++) { ReferenceConstraint rc = (ReferenceConstraint) constraints[j]; QName ntName = rc.getNodeTypeName(); - if (!name.equals(ntName) && !registeredNTDefs.containsKey(ntName)) { + if (!name.equals(ntName) && !aRegisteredNTDefCache.containsKey(ntName)) { String msg = "[" + name + "#" + pd.getName() + "] invalid REFERENCE value constraint '" + ntName + "' (unknown node type)"; @@ -689,7 +651,7 @@ * the default primary type must be registered, with one notable * exception: the node type just being registered */ - if (!name.equals(dpt) && !registeredNTDefs.containsKey(dpt)) { + if (!name.equals(dpt) && !aRegisteredNTDefCache.containsKey(dpt)) { String msg = "[" + name + "#" + cnd.getName() + "] invalid default primary type '" + dpt + "'"; log.debug(msg); @@ -701,14 +663,14 @@ */ try { if (!referenceToSelf) { - defaultENT = getEffectiveNodeType(dpt); + defaultENT = getEffectiveNodeType(dpt, anEntCache, aRegisteredNTDefCache); } else { /** * the default primary type is identical with the node * type just being registered; we have to instantiate it * 'manually' */ - ent = EffectiveNodeType.create(this, ntd); + ent = EffectiveNodeType.create(this, ntd, anEntCache, aRegisteredNTDefCache); defaultENT = ent; } if (cnd.isAutoCreated()) { @@ -719,7 +681,7 @@ */ Stack definingNTs = new Stack(); definingNTs.push(name); - checkForCircularNodeAutoCreation(defaultENT, definingNTs); + checkForCircularNodeAutoCreation(defaultENT, definingNTs, anEntCache, aRegisteredNTDefCache); } } catch (NodeTypeConflictException ntce) { String msg = "[" + name + "#" + cnd.getName() @@ -752,7 +714,7 @@ * the required primary type must be registered, with one * notable exception: the node type just being registered */ - if (!name.equals(rpt) && !registeredNTDefs.containsKey(rpt)) { + if (!name.equals(rpt) && !aRegisteredNTDefCache.containsKey(rpt)) { String msg = "[" + name + "#" + cnd.getName() + "] invalid required primary type: " + rpt; log.debug(msg); @@ -775,7 +737,7 @@ */ try { if (!referenceToSelf) { - getEffectiveNodeType(rpt); + getEffectiveNodeType(rpt, anEntCache, aRegisteredNTDefCache); } else { /** * the required primary type is identical with the @@ -783,7 +745,7 @@ * instantiate it 'manually' */ if (ent == null) { - ent = EffectiveNodeType.create(this, ntd); + ent = EffectiveNodeType.create(this, ntd, anEntCache, aRegisteredNTDefCache); } } } catch (NodeTypeConflictException ntce) { @@ -808,7 +770,7 @@ */ if (ent == null) { try { - ent = EffectiveNodeType.create(this, ntd); + ent = EffectiveNodeType.create(this, ntd, anEntCache, aRegisteredNTDefCache); } catch (NodeTypeConflictException ntce) { String msg = "[" + name + "] failed to resolve node type definition"; log.debug(msg); @@ -823,45 +785,40 @@ } /** - * Returns the names of all registered node types. That includes primary - * and mixin node types. - * - * @return the names of all registered node types. - */ - public synchronized QName[] getRegisteredNodeTypes() { - return (QName[]) registeredNTDefs.keySet().toArray(new QName[registeredNTDefs.size()]); - } - - /** - * @return - */ - public NodeDef getRootNodeDef() { - return rootNodeDef; - } - - /** * @param ntName + * @param anEntCache + * @param aRegisteredNTDefCache * @return * @throws NoSuchNodeTypeException */ - public synchronized EffectiveNodeType getEffectiveNodeType(QName ntName) + public synchronized EffectiveNodeType getEffectiveNodeType(QName ntName, EffectiveNodeTypeCache anEntCache, Map aRegisteredNTDefCache) throws NoSuchNodeTypeException { - // 1. make sure that the specified node type is registered - if (!registeredNTDefs.containsKey(ntName)) { + // 1. make sure that the specified node type exists + if (!aRegisteredNTDefCache.containsKey(ntName)) { throw new NoSuchNodeTypeException(ntName.toString()); } // 2. check if effective node type has already been built - WeightedKey key = new WeightedKey(new QName[]{ntName}); - if (entCache.contains(key)) { - return entCache.get(key); + EffectiveNodeType ent = anEntCache.get(new QName[]{ntName}); + if (ent != null) { + return ent; } // 3. build effective node type try { - EffectiveNodeType ent = EffectiveNodeType.create(this, ntName); + NodeTypeDef def = (NodeTypeDef) aRegisteredNTDefCache.get(ntName); + NodeTypeDef ntDef4ENT; + // return clone to make sure nobody messes around with the 'live' definition + try { + ntDef4ENT = (NodeTypeDef) def.clone(); + } catch (CloneNotSupportedException e) { + // should never get here + log.fatal("internal error", e); + throw new InternalError(e.getMessage()); + } + ent = EffectiveNodeType.create(this, ntDef4ENT, anEntCache, aRegisteredNTDefCache); // store new effective node type - entCache.put(ent); + anEntCache.put(ent); return ent; } catch (NodeTypeConflictException ntce) { // should never get here as all registered node types have to be valid! @@ -873,24 +830,27 @@ /** * @param ntNames + * @param anEntCache + * @param aRegisteredNTDefCache * @return * @throws NodeTypeConflictException * @throws NoSuchNodeTypeException */ - public synchronized EffectiveNodeType getEffectiveNodeType(QName[] ntNames) + public synchronized EffectiveNodeType getEffectiveNodeType(QName[] ntNames, EffectiveNodeTypeCache anEntCache, Map aRegisteredNTDefCache) throws NodeTypeConflictException, NoSuchNodeTypeException { - // 1. make sure every single node type is registered + // 1. make sure every single node type exists for (int i = 0; i < ntNames.length; i++) { - if (!registeredNTDefs.containsKey(ntNames[i])) { + if (!aRegisteredNTDefCache.containsKey(ntNames[i])) { throw new NoSuchNodeTypeException(ntNames[i].toString()); } } - WeightedKey key = new WeightedKey(ntNames); + EffectiveNodeTypeCache.WeightedKey key = + new EffectiveNodeTypeCache.WeightedKey(ntNames); // 2. check if aggregate has already been built - if (entCache.contains(key)) { - return entCache.get(key); + if (anEntCache.contains(key)) { + return anEntCache.get(key); } // 3. build aggregate @@ -900,8 +860,8 @@ ArrayList tmpResults = new ArrayList(); while (key.size() > 0) { // check if we've already built this aggregate - if (entCache.contains(key)) { - tmpResults.add(entCache.get(key)); + if (anEntCache.contains(key)) { + tmpResults.add(anEntCache.get(key)); // subtract the result from the temporary key // (which is 'empty' now) key = key.subtract(key); @@ -912,15 +872,16 @@ * aggregate (i.e. the cost of building it) */ boolean foundSubResult = false; - Iterator iter = entCache.keys(); + Iterator iter = anEntCache.keys(); while (iter.hasNext()) { - WeightedKey k = (WeightedKey) iter.next(); + EffectiveNodeTypeCache.WeightedKey k = + (EffectiveNodeTypeCache.WeightedKey) iter.next(); /** * check if the existing aggregate is a 'subset' of the one * we're looking for */ if (key.contains(k)) { - tmpResults.add(entCache.get(k)); + tmpResults.add(anEntCache.get(k)); // subtract the result from the temporary key key = key.subtract(k); foundSubResult = true; @@ -934,16 +895,27 @@ */ QName[] remainder = key.toArray(); for (int i = 0; i < remainder.length; i++) { + NodeTypeDef def = (NodeTypeDef) aRegisteredNTDefCache.get(remainder[i]); + NodeTypeDef clonedDef; + // return clone to make sure nobody messes around with the 'live' definition + try { + clonedDef = (NodeTypeDef) def.clone(); + } catch (CloneNotSupportedException e) { + // should never get here + log.fatal("internal error", e); + throw new InternalError(e.getMessage()); + } + EffectiveNodeType ent = - EffectiveNodeType.create(this, remainder[i]); + EffectiveNodeType.create(this, clonedDef, anEntCache, aRegisteredNTDefCache); // store new effective node type - entCache.put(ent); + anEntCache.put(ent); if (result == null) { result = ent; } else { result = result.merge(ent); // store intermediate result (sub-aggregate) - entCache.put(result); + anEntCache.put(result); } } // add aggregate of remaining node types to result list @@ -958,14 +930,52 @@ } else { result = result.merge((EffectiveNodeType) tmpResults.get(i)); // store intermediate result - entCache.put(result); + anEntCache.put(result); } } // we're done return result; } - void checkForCircularInheritance(QName[] supertypes, Stack inheritanceChain) + /** + * Returns the names of all registered node types. That includes primary + * and mixin node types. + * + * @return the names of all registered node types. + */ + public synchronized QName[] getRegisteredNodeTypes() { + return (QName[]) registeredNTDefs.keySet().toArray(new QName[registeredNTDefs.size()]); + } + + /** + * @return + */ + public NodeDef getRootNodeDef() { + return rootNodeDef; + } + + /** + * @param ntName + * @return + * @throws NoSuchNodeTypeException + */ + public synchronized EffectiveNodeType getEffectiveNodeType(QName ntName) + throws NoSuchNodeTypeException { + return getEffectiveNodeType(ntName, entCache, registeredNTDefs); + } + + /** + * @param ntNames + * @return + * @throws NodeTypeConflictException + * @throws NoSuchNodeTypeException + */ + public synchronized EffectiveNodeType getEffectiveNodeType(QName[] ntNames) + throws NodeTypeConflictException, NoSuchNodeTypeException { + return getEffectiveNodeType(ntNames, entCache, registeredNTDefs); + } + + void checkForCircularInheritance(QName[] supertypes, Stack inheritanceChain, Map aRegisteredNTDefCache) throws InvalidNodeTypeDefException, RepositoryException { for (int i = 0; i < supertypes.length; i++) { QName nt = supertypes[i]; @@ -985,11 +995,13 @@ } try { - QName[] sta = getNodeTypeDef(nt).getSupertypes(); + + NodeTypeDef ntd = (NodeTypeDef) aRegisteredNTDefCache.get(nt); + QName[] sta = ntd.getSupertypes(); if (sta != null && sta.length > 0) { // check recursively inheritanceChain.push(nt); - checkForCircularInheritance(sta, inheritanceChain); + checkForCircularInheritance(sta, inheritanceChain, aRegisteredNTDefCache); inheritanceChain.pop(); } } catch (NoSuchNodeTypeException nsnte) { @@ -1001,7 +1013,7 @@ } void checkForCircularNodeAutoCreation(EffectiveNodeType childNodeENT, - Stack definingParentNTs) + Stack definingParentNTs, EffectiveNodeTypeCache anEntCache, Map aRegisteredNTDefCache) throws InvalidNodeTypeDefException { // check for circularity through default node types of auto-created child nodes // (node type 'a' defines auto-created child node with default node type 'a') @@ -1035,8 +1047,8 @@ if (dnt != null) { // check recursively definingParentNTs.push(definingNT); - checkForCircularNodeAutoCreation(getEffectiveNodeType(dnt), - definingParentNTs); + checkForCircularNodeAutoCreation(getEffectiveNodeType(dnt, anEntCache, aRegisteredNTDefCache), + definingParentNTs, anEntCache, aRegisteredNTDefCache); definingParentNTs.pop(); } } catch (NoSuchNodeTypeException nsnte) { @@ -1110,22 +1122,18 @@ * Note that in the case an exception is thrown, some node types might have * been nevertheless successfully registered. * - * @param ntDefs a collection of NodeTypeDefs + * @param newNTDefs a collection of NodeTypeDefs * @throws InvalidNodeTypeDefException * @throws RepositoryException */ - public synchronized void registerNodeTypes(Collection ntDefs) + public synchronized void registerNodeTypes(Collection newNTDefs) throws InvalidNodeTypeDefException, RepositoryException { // exceptions that might be thrown by internalRegister(Collection) RepositoryException re = null; InvalidNodeTypeDefException intde = null; - // store names of currently registered node types before proceeding - HashSet oldNTNames = new HashSet(registeredNTDefs.keySet()); - try { - // validate and register new node type definitions - internalRegister(ntDefs); + internalRegister(newNTDefs); } catch (RepositoryException e) { // store exception so it can be re-thrown later on re = e; @@ -1133,35 +1141,107 @@ // store exception so it can be re-thrown later on intde = e; } - - /** - * build set of names of actually registered new node types - * (potentially a subset of those specified in ntDefs if an exception - * had been thrown) - */ - HashSet newNTNames = new HashSet(registeredNTDefs.keySet()); - newNTNames.removeAll(oldNTNames); - - if (newNTNames.size() > 0) { - // persist new node type definitions - for (Iterator iter = newNTNames.iterator(); iter.hasNext();) { - QName ntName = (QName) iter.next(); - customNTDefs.add((NodeTypeDef) registeredNTDefs.get(ntName)); + boolean allNodeTypeDefsAreValid = re == null && intde == null; + if (allNodeTypeDefsAreValid) { + Iterator validNTDsIterator = newNTDefs.iterator(); + while (validNTDsIterator.hasNext()) { + NodeTypeDef ntd = (NodeTypeDef) validNTDsIterator.next(); + // store property & child node definitions of new node type by id + customNTDefs.add(ntd); } persistCustomNodeTypeDefs(customNTDefs); - // notify listeners - for (Iterator iter = newNTNames.iterator(); iter.hasNext();) { - QName ntName = (QName) iter.next(); - notifyRegistered(ntName); + for (Iterator iter = newNTDefs.iterator(); iter.hasNext();) { + NodeTypeDef ntDef = (NodeTypeDef) iter.next(); + notifyRegistered(ntDef.getName()); + } + } else { + // re-throw the exception + if (re != null) { + throw re; + } else if (intde != null) { + throw intde; } } + } - // re-throw exception as necessary - if (re != null) { - throw re; - } else if (intde != null) { - throw intde; + /** + * Validates and registers the specified collection of NodeTypeDef + * objects. An InvalidNodeTypeDefException is thrown if the + * validation of any of the contained NodeTypeDef objects fails. + *

+ * Note that in the case an exception is thrown no node type will be + * registered. + * + * @param newNTDefs collection of NodeTypeDef objects + * @throws InvalidNodeTypeDefException + * @throws RepositoryException + * @see #registerNodeType + */ + private synchronized void internalRegister(Collection newNTDefs) + throws InvalidNodeTypeDefException, RepositoryException { + + // cache of pre-built aggregations of node types + EffectiveNodeTypeCache anEntCache = new EffectiveNodeTypeCache(entCache); + + // map of node type names and node type definitions + Map aRegisteredNTDefCache = new HashMap(registeredNTDefs); + + // temporarily register a clone of the node type definition + // and do some checks by the way + Iterator ntdNameIterator = newNTDefs.iterator(); + while (ntdNameIterator.hasNext()) { + Object ntdObject = ntdNameIterator.next(); + // check if the right type is used + if (!(ntdObject instanceof NodeTypeDef)) { + String msg = "The specified object is not of type " + + NodeTypeDef.class.getName(); + log.debug(msg); + throw new InvalidNodeTypeDefException(msg); + } else { + // check if the ntd is new + NodeTypeDef ntd = (NodeTypeDef) ntdObject; + QName name = ntd.getName(); + if (name != null && registeredNTDefs.containsKey(name)) { + String msg = name + " already exists"; + log.debug(msg); + throw new InvalidNodeTypeDefException(msg); + } + // clone the ntd and add it to the cache + NodeTypeDef clonedNTD; + try { + clonedNTD = (NodeTypeDef) ntd.clone(); + } catch (CloneNotSupportedException e) { + // should never get here + log.fatal("internal error", e); + throw new InternalError(e.getMessage()); + } + aRegisteredNTDefCache.put(clonedNTD.getName(), clonedNTD); + } + } + Iterator ntdIterator = newNTDefs.iterator(); + while (ntdIterator.hasNext()) { + NodeTypeDef ntd = (NodeTypeDef) ntdIterator.next(); + + EffectiveNodeType ent = validateNodeTypeDef(ntd, anEntCache, aRegisteredNTDefCache); + + // store new effective node type instance + anEntCache.put(ent); + } + // as no exception occured at this point, the ntds are valid + Iterator validNTDsIterator = newNTDefs.iterator(); + while (validNTDsIterator.hasNext()) { + NodeTypeDef ntd = (NodeTypeDef) validNTDsIterator.next(); + registeredNTDefs.put(ntd.getName(), ntd); + // store property & child node definitions of new node type by id + PropDef[] pda = ntd.getPropertyDefs(); + for (int i = 0; i < pda.length; i++) { + propDefs.put(pda[i].getId(), pda[i]); + } + NodeDef[] nda = ntd.getChildNodeDefs(); + for (int i = 0; i < nda.length; i++) { + nodeDefs.put(nda[i].getId(), nda[i]); + } } } @@ -1232,7 +1312,7 @@ /** * validate new node type definition */ - validateNodeTypeDef(ntd); + validateNodeTypeDef(ntd, this.entCache, this.registeredNTDefs); /** * build diff of current and new definition and determine type of change @@ -1320,7 +1400,7 @@ * * @param nodeTypeName name of node type whose definition should be returned. * @return the node type definition of the node type with the given name. - * @throws NoSuchNodeTypeException if a node type with the given name + * @throws NoSuchNodeTypeException if a node type with the given name * does not exist */ public synchronized NodeTypeDef getNodeTypeDef(QName nodeTypeName) @@ -1659,268 +1739,5 @@ protected void checkForReferencesInContent(QName nodeTypeName) throws RepositoryException { throw new RepositoryException("not yet implemented"); - } - - //--------------------------------------------------------< inner classes > - /** - * A WeightedKey uniquely identifies - * a combination (i.e. an aggregation) of one or more node types. - * The weight is an indicator for the cost involved in building such an - * aggregate (e.g. an aggregation of multiple complex node types with deep - * inheritance trees is more costly to build/validate than an agreggation - * of two very simple node types with just one property definition each). - *

- * A very simple (and not very accurate) approximation of the weight would - * be the number of explicitly aggregated node types (ignoring inheritance - * and complexity of each involved node type). A better approximation would - * be the number of all, explicitly and implicitly (note that - * inheritance is also an aggregation) aggregated node types. - *

- * The more accurate the weight definition, the more efficient is the - * the building of new aggregates. - *

- * It is important to note that the weight is not part of the key value, - * i.e. it is not considered by the hashCode() and - * equals(Object) methods. It does however affect the order - * of WeightedKey instances. See - * {@link #compareTo(Object)} for more information. - *

- * Let's assume we have an aggregation of node types named "b", "a" and "c". - * Its key would be "[a, b, c]" and the weight 3 (using the simple - * approximation). - */ - static class WeightedKey implements Comparable { - /** - * set of node type names, sorted in ascending order - */ - private final TreeSet set; - private final int weight; - - /** - * @param ntNames - */ - WeightedKey(QName[] ntNames) { - this(ntNames, ntNames.length); - } - - /** - * @param ntNames - * @param weight - */ - WeightedKey(QName[] ntNames, int weight) { - this.weight = weight; - - set = new TreeSet(); - for (int i = 0; i < ntNames.length; i++) { - // add name to this sorted set - set.add(ntNames[i]); - } - } - - /** - * @param ntNames - */ - WeightedKey(Collection ntNames) { - this(ntNames, ntNames.size()); - } - - /** - * @param ntNames - * @param weight - */ - WeightedKey(Collection ntNames, int weight) { - this.weight = weight; - set = new TreeSet(ntNames); - } - - /** - * The key is the string representation of this sorted set - * (e.g. the key for a set containing entries "c", "b" and "a" would - * be "[a, b, c]"). - * - * @return string representation of this sorted set - * @see java.util.AbstractCollection#toString - */ - String getKey() { - return set.toString(); - } - - /** - * @return - */ - int getWeight() { - return weight; - } - - int size() { - return set.size(); - } - - Iterator iterator() { - return Collections.unmodifiableSortedSet(set).iterator(); - } - - Set getSet() { - return Collections.unmodifiableSortedSet(set); - } - - QName[] toArray() { - return (QName[]) set.toArray(new QName[set.size()]); - } - - boolean contains(WeightedKey otherKey) { - return set.containsAll(otherKey.getSet()); - } - - WeightedKey subtract(WeightedKey otherKey) { - Set tmp = (Set) set.clone(); - tmp.removeAll(otherKey.getSet()); - return new WeightedKey(tmp); - - } - - /** - * The resulting sort-order is: 1. descending weight, 2. ascending key - * (i.e. string representation of this sorted set). - * - * @param o - * @return - */ - public int compareTo(Object o) { - WeightedKey other = (WeightedKey) o; - if (getWeight() > other.getWeight()) { - return -1; - } else if (getWeight() < other.getWeight()) { - return 1; - } - return getKey().compareTo(other.getKey()); - } - - public int hashCode() { - int h = 17; - // ignore weight - Iterator i = set.iterator(); - while (i.hasNext()) { - h *= 37; - Object obj = i.next(); - if (obj != null) { - h += obj.hashCode(); - } - } - return h; - } - - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj instanceof WeightedKey) { - WeightedKey other = (WeightedKey) obj; - // ignore weight - return set.equals(other.set); - } - return false; - } - - public String toString() { - return set.toString() + " (" + weight + ")"; - } - } - - /** - * EfectiveNodeTypeCache ... - */ - private class EffectiveNodeTypeCache { - // ordered set of keys - final TreeSet sortedKeys; - // cache of pre-build aggregations of node types - final HashMap aggregates; - - EffectiveNodeTypeCache() { - sortedKeys = new TreeSet(); - aggregates = new HashMap(); - } - - void put(EffectiveNodeType ent) { - // we define the weight as the total number of included node types - // (through aggregation and inheritance) - int weight = ent.getAllNodeTypes().length; - // the effective node type is identified by the list of merged - // (i.e. aggregated) node types - WeightedKey k = new WeightedKey(ent.getMergedNodeTypes(), weight); - aggregates.put(k, ent); - sortedKeys.add(k); - } - - boolean contains(QName[] ntNames) { - return aggregates.containsKey(new WeightedKey(ntNames)); - } - - boolean contains(WeightedKey key) { - return aggregates.containsKey(key); - } - - EffectiveNodeType get(QName[] ntNames) { - return (EffectiveNodeType) aggregates.get(new WeightedKey(ntNames)); - } - - EffectiveNodeType get(WeightedKey key) { - return (EffectiveNodeType) aggregates.get(key); - } - - EffectiveNodeType remove(QName[] ntNames) { - return remove(new WeightedKey(ntNames)); - } - - EffectiveNodeType remove(WeightedKey key) { - EffectiveNodeType removed = (EffectiveNodeType) aggregates.remove(key); - if (removed != null) { - // remove index entry - - // FIXME: can't simply call TreeSet.remove(key) because the entry - // in sortedKeys might have a different weight and would thus - // not be found - Iterator iter = sortedKeys.iterator(); - while (iter.hasNext()) { - WeightedKey k = (WeightedKey) iter.next(); - // WeightedKey.equals(Object) ignores the weight - if (key.equals(k)) { - sortedKeys.remove(k); - break; - } - } - } - return removed; - } - - /** - * Returns an iterator over the keys. The order of the returned keys is: - *

    - *
  • 1. descending weight
  • - *
  • 2. ascending key (i.e. unique identifier of aggregate)
  • - *
- * - * @see NodeTypeRegistry.WeightedKey#compareTo - */ - Iterator keys() { - return sortedKeys.iterator(); - } - - //---------------------------------------------------------< Dumpable > - /** - * {@inheritDoc} - */ - public void dump(PrintStream ps) { - ps.println("EffectiveNodeTypeCache (" + this + ")"); - ps.println(); - ps.println("EffectiveNodeTypes in cache:"); - ps.println(); - Iterator iter = sortedKeys.iterator(); - while (iter.hasNext()) { - WeightedKey k = (WeightedKey) iter.next(); - //EffectiveNodeType ent = (EffectiveNodeType) aggregates.get(k); - ps.println(k); - } - } } } Added: incubator/jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/nodetype/CyclicNodeTypeRegistrationTest.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/nodetype/CyclicNodeTypeRegistrationTest.java?rev=373516&view=auto ============================================================================== --- incubator/jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/nodetype/CyclicNodeTypeRegistrationTest.java (added) +++ incubator/jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/nodetype/CyclicNodeTypeRegistrationTest.java Mon Jan 30 06:07:41 2006 @@ -0,0 +1,245 @@ +/* + * Copyright 2004-2005 The Apache Software Foundation or its licensors, + * as applicable. + * + * 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.apache.jackrabbit.core.nodetype; + +import org.apache.jackrabbit.name.QName; +import org.apache.jackrabbit.test.AbstractJCRTest; + +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeTypeManager; +import java.util.Collection; +import java.util.LinkedList; + +/** + * @test + * @sources CyclicNodeTypeRegistrationTest + * @executeClass org.apache.jackrabbit.test.api.nodetype.CyclicNodeTypeRegistrationTest + * @keywords level1 + */ +public class CyclicNodeTypeRegistrationTest extends AbstractJCRTest { + /** + * The session we use for the tests + */ + private Session session; + + /** + * The node type manager we use for the tests + */ + private NodeTypeManager manager; + + /** + * The node type registry we use for the tests + */ + private NodeTypeRegistry ntreg; + + /** + * The cyclic dependent node type definitions we use for the tests + */ + private Collection ntDefCollection; + + /** + * Sets up the fixture for the test cases. + */ + protected void setUp() throws Exception { + //isReadOnly = true; + super.setUp(); + + session = helper.getReadOnlySession(); + manager = session.getWorkspace().getNodeTypeManager(); + + // Get the NodeTypeManager from the Workspace. + // Note that it must be cast from the generic JCR NodeTypeManager to the + // Jackrabbit-specific implementation. + NodeTypeManagerImpl ntmgr = (NodeTypeManagerImpl) session.getWorkspace().getNodeTypeManager(); + + // Acquire the NodeTypeRegistry + ntreg = ntmgr.getNodeTypeRegistry(); + + + } + + /** + * Releases the session aquired in {@link #setUp()}. + */ + protected void tearDown() throws Exception { + if (session != null) { + session.logout(); + } + super.tearDown(); + } + + /** + * Tests, if it is possible to register node types with simple + * cyclic dependencies. + */ + public void testRegisterCyclicChildNodeTypes() { + /** + * Constructs node types with the following structure: + * [foo] + * + myBarInFoo (bar) + * + * [bar] + * + myFooInBar (foo) + */ + final NodeTypeDef foo = new NodeTypeDef(); + foo.setName(new QName("", "foo")); + foo.setSupertypes(new QName[]{QName.NT_BASE}); + + final NodeTypeDef bar = new NodeTypeDef(); + bar.setName(new QName("", "bar")); + bar.setSupertypes(new QName[]{QName.NT_BASE}); + + NodeDefImpl myBarInFoo = new NodeDefImpl(); + myBarInFoo.setRequiredPrimaryTypes(new QName[]{bar.getName()}); + myBarInFoo.setName(new QName("", "myBarInFoo")); + myBarInFoo.setDeclaringNodeType(foo.getName()); + + NodeDefImpl myFooInBar = new NodeDefImpl(); + myFooInBar.setRequiredPrimaryTypes(new QName[]{foo.getName()}); + myFooInBar.setName(new QName("", "myFooInBar")); + myFooInBar.setDeclaringNodeType(bar.getName()); + + foo.setChildNodeDefs(new NodeDefImpl[]{myBarInFoo}); + bar.setChildNodeDefs(new NodeDefImpl[]{myFooInBar}); + ntDefCollection = new LinkedList(); + ntDefCollection.add(foo); + ntDefCollection.add(bar); + + try { + ntreg.registerNodeTypes(ntDefCollection); + } catch (InvalidNodeTypeDefException e) { + assertFalse(e.getMessage(), true); + e.printStackTrace(); + } catch (RepositoryException e) { + assertFalse(e.getMessage(), true); + e.printStackTrace(); + } + boolean allNTsAreRegistered = ntreg.isRegistered(foo.getName()) && ntreg.isRegistered(bar.getName()); + assertTrue(allNTsAreRegistered); + + } + + /** + * A simple check, if a missing node type is found + */ + public void testRegisterSimpleMissingNodeTypes() { + /** + * Constructs node types with the following structure: + * [foo] + * + myNTInFoo (I_am_an_invalid_required_primary_type) + * + */ + final NodeTypeDef foo = new NodeTypeDef(); + foo.setName(new QName("", "foo")); + foo.setSupertypes(new QName[]{QName.NT_BASE}); + + + NodeDefImpl myBarInFoo = new NodeDefImpl(); + myBarInFoo.setRequiredPrimaryTypes(new QName[]{new QName("", "I_am_an_invalid_required_primary_type")}); + myBarInFoo.setName(new QName("", "myNTInFoo")); + myBarInFoo.setDeclaringNodeType(foo.getName()); + + foo.setChildNodeDefs(new NodeDefImpl[]{myBarInFoo}); + ntDefCollection = new LinkedList(); + ntDefCollection.add(foo); + + try { + ntreg.registerNodeTypes(ntDefCollection); + assertFalse("Missing node type not found", true); + } catch (InvalidNodeTypeDefException e) { + assertTrue(true); + + } catch (RepositoryException e) { + assertFalse("Wrong Exception thrown on missing node type.", true); + e.printStackTrace(); + } + } + + /** + * Basically a test of a Graffito use case. + */ + public void testRegisterCyclicChildNodeTypesAndSupertypes() { + /** + * Constructs node types with the following structure: + * [Folder] > CmsObject + * + folders (Folder) + * + documents (Document) + * + * [CmsObject] + * + parentFolder (Folder) + * + * [Document] > CmsObject + * - size (long) + * + */ + + final NodeTypeDef folder = new NodeTypeDef(); + folder.setName(new QName("", "Folder")); + + final NodeTypeDef cmsObject = new NodeTypeDef(); + cmsObject.setName(new QName("", "CmsObject")); + cmsObject.setSupertypes(new QName[]{QName.NT_BASE}); + NodeDefImpl parentFolder = new NodeDefImpl(); + parentFolder.setRequiredPrimaryTypes(new QName[]{folder.getName()}); + parentFolder.setName(new QName("", "parentFolder")); + parentFolder.setDeclaringNodeType(cmsObject.getName()); + cmsObject.setChildNodeDefs(new NodeDefImpl[]{parentFolder}); + + + final NodeTypeDef document = new NodeTypeDef(); + document.setName(new QName("", "Document")); + document.setSupertypes(new QName[]{cmsObject.getName()}); + PropDefImpl sizeProp = new PropDefImpl(); + sizeProp.setName(new QName("", "size")); + sizeProp.setRequiredType(PropertyType.LONG); + sizeProp.setDeclaringNodeType(document.getName()); + document.setPropertyDefs(new PropDef[]{sizeProp}); + + + folder.setSupertypes(new QName[]{cmsObject.getName()}); + + NodeDefImpl folders = new NodeDefImpl(); + folders.setRequiredPrimaryTypes(new QName[]{folder.getName()}); + folders.setName(new QName("", "folders")); + folders.setDeclaringNodeType(folder.getName()); + + NodeDefImpl documents = new NodeDefImpl(); + documents.setRequiredPrimaryTypes(new QName[]{document.getName()}); + documents.setName(new QName("", "documents")); + documents.setDeclaringNodeType(folder.getName()); + + folder.setChildNodeDefs(new NodeDefImpl[]{folders, documents}); + ntDefCollection = new LinkedList(); + ntDefCollection.add(folder); + ntDefCollection.add(document); + ntDefCollection.add(cmsObject); + + try { + ntreg.registerNodeTypes(ntDefCollection); + } catch (InvalidNodeTypeDefException e) { + assertFalse(e.getMessage(), true); + e.printStackTrace(); + } catch (RepositoryException e) { + assertFalse(e.getMessage(), true); + e.printStackTrace(); + } + boolean allNTsAreRegistered = ntreg.isRegistered(folder.getName()) && ntreg.isRegistered(document.getName()) && ntreg.isRegistered(cmsObject.getName()); + assertTrue(allNTsAreRegistered); + } +} \ No newline at end of file Propchange: incubator/jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/nodetype/CyclicNodeTypeRegistrationTest.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: incubator/jackrabbit/trunk/jackrabbit/src/test/java/org/apache/jackrabbit/core/nodetype/CyclicNodeTypeRegistrationTest.java ------------------------------------------------------------------------------ svn:keywords = author date id revision url